From cdd477fe8661243042131a98411337fd515d7e9b Mon Sep 17 00:00:00 2001 From: Ali Mohammad Date: Fri, 13 Nov 2026 23:32:40 +0300 Subject: [PATCH 01/12] Rename SDK to @linuxdevil/lousho and publish Studio as @linuxdevil/studio 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 ^ 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> --- .fallowrc.json | 7 +- .github/pull_request_template.md | 2 +- .github/workflows/ci.yml | 2 +- .gitignore | 3 + CHANGELOG.md | 5 +- README.md | 6 +- api/auth.api.md | 2 +- api/executor.api.md | 2 +- api/flows.api.md | 2 +- api/hooks.api.md | 2 +- api/improve.api.md | 2 +- api/index.api.md | 2 +- api/integrations.api.md | 2 +- api/kv.api.md | 2 +- api/mcp.api.md | 2 +- api/observe.api.md | 2 +- api/otel.api.md | 2 +- api/react.api.md | 2 +- api/spec.api.md | 2 +- api/sqlite.api.md | 2 +- api/svelte.api.md | 2 +- api/testing.api.md | 2 +- api/tools.api.md | 2 +- api/traces.api.md | 2 +- api/triggers.api.md | 2 +- api/types.api.md | 2 +- api/utils.api.md | 2 +- api/vue.api.md | 2 +- api/worker.api.md | 2 +- apps/agent-forge/LICENSE | 21 + apps/agent-forge/README.md | 53 + apps/agent-forge/eslintRules/noSdkSource.mjs | 96 +- apps/agent-forge/evals/author/harness.ts | 4 +- apps/agent-forge/evals/author/judge.ts | 2 +- apps/agent-forge/evals/author/run.ts | 2 +- apps/agent-forge/package.json | 51 +- apps/agent-forge/scripts/pin-sdk-range.mjs | 59 + .../server/__tests__/abort.test.ts | 2 +- .../__tests__/agentRequirements.test.ts | 2 +- .../__tests__/agentRoutes.create.test.ts | 2 +- .../server/__tests__/agentRoutes.test.ts | 2 +- .../server/__tests__/allAgentsRoute.test.ts | 2 +- apps/agent-forge/server/__tests__/app.test.ts | 2 +- .../server/__tests__/approvalFlow.test.ts | 4 +- .../server/__tests__/authorEnable.test.ts | 2 +- .../server/__tests__/authorSave.test.ts | 2 +- .../server/__tests__/authorSecret.test.ts | 2 +- .../server/__tests__/authorSessions.test.ts | 2 +- .../server/__tests__/authorStream.test.ts | 4 +- .../server/__tests__/autoApproval.test.ts | 2 +- .../server/__tests__/bridgeAgentHost.test.ts | 5 +- .../server/__tests__/bridgeClient.test.ts | 2 +- .../server/__tests__/bridgeGuards.test.ts | 2 +- .../server/__tests__/buildAgentV2.test.ts | 2 +- .../server/__tests__/builderReporting.test.ts | 2 +- .../server/__tests__/catalogRoutes.test.ts | 2 +- .../server/__tests__/chainTriggers.test.ts | 4 +- .../server/__tests__/channelTriggers.test.ts | 8 +- .../agent-forge/server/__tests__/chat.test.ts | 2 +- .../server/__tests__/chatReconcile.test.ts | 2 +- .../server/__tests__/checkpointStore.test.ts | 2 +- apps/agent-forge/server/__tests__/cli.test.ts | 125 ++ .../server/__tests__/collector.test.ts | 2 +- .../server/__tests__/copyAgent.test.ts | 2 +- .../server/__tests__/cronTriggers.test.ts | 2 +- .../server/__tests__/deployRunner.test.ts | 2 +- .../server/__tests__/draftRoutes.test.ts | 2 +- .../server/__tests__/draftView.test.ts | 2 +- .../server/__tests__/eventsAll.test.ts | 2 +- .../server/__tests__/eventsWs.test.ts | 2 +- .../server/__tests__/export.test.ts | 2 +- .../__tests__/fixtures/scriptedProvider.mjs | 37 +- .../server/__tests__/hooksRoute.test.ts | 4 +- .../server/__tests__/hostRoutes.test.ts | 530 +++---- .../server/__tests__/hostSelector.test.ts | 422 +++--- .../server/__tests__/idleVersions.test.ts | 2 +- .../server/__tests__/importRoutes.test.ts | 2 +- .../__tests__/inProcessAgentHost.test.ts | 2 +- .../server/__tests__/inboxRoutes.test.ts | 2 +- .../server/__tests__/judge.test.ts | 2 +- .../server/__tests__/localTools.test.ts | 2 +- .../server/__tests__/mcpProbe.test.ts | 4 +- .../__tests__/missingProviderKey.test.ts | 2 +- .../server/__tests__/multiWorkspace.test.ts | 2 +- .../server/__tests__/multiWorkspaceWs.test.ts | 2 +- .../server/__tests__/noSdkSource.test.ts | 14 +- .../server/__tests__/nodeFrames.test.ts | 2 +- .../server/__tests__/nodeStates.test.ts | 2 +- .../server/__tests__/observeReporting.test.ts | 2 +- .../server/__tests__/pinSdkRange.test.ts | 112 ++ .../agent-forge/server/__tests__/pins.test.ts | 6 +- .../server/__tests__/playground.test.ts | 2 +- .../__tests__/portabilityRoutes.test.ts | 2 +- .../server/__tests__/publishRoute.test.ts | 2 +- .../server/__tests__/questions.test.ts | 4 +- .../server/__tests__/ratings.test.ts | 2 +- .../server/__tests__/runRegistry.test.ts | 2 +- .../server/__tests__/runSource.test.ts | 2 +- .../server/__tests__/sdkRoot.test.ts | 61 + .../server/__tests__/steer.test.ts | 2 +- .../agent-forge/server/__tests__/steerGate.ts | 2 +- .../server/__tests__/suggestionRoutes.test.ts | 2 +- .../server/__tests__/timeTravel.test.ts | 2 +- .../server/__tests__/traces.test.ts | 4 +- .../server/__tests__/triggerHost.test.ts | 4 +- .../server/__tests__/versionRoutes.test.ts | 2 +- .../__tests__/workspaceScanRoute.test.ts | 2 +- .../server/__tests__/workspaceSdk.test.ts | 202 +-- .../server/__tests__/workspaceSeam.test.ts | 4 +- .../server/abortableProvider.test.ts | 2 +- apps/agent-forge/server/abortableProvider.ts | 2 +- apps/agent-forge/server/app.ts | 4 +- apps/agent-forge/server/approvalStore.ts | 2 +- .../server/author/__tests__/provider.test.ts | 2 +- .../author/__tests__/requirements.test.ts | 2 +- .../server/author/__tests__/saveFlow.test.ts | 4 +- .../author/__tests__/turnContext.test.ts | 2 +- .../author/__tests__/turnRunner.test.ts | 4 +- .../author/draft/__tests__/toSpec.test.ts | 2 +- .../agent-forge/server/author/draft/schema.ts | 4 +- .../agent-forge/server/author/draft/toSpec.ts | 4 +- .../server/author/modelMessages.ts | 2 +- .../proposal/__tests__/catalogChecks.test.ts | 2 +- .../author/proposal/__tests__/gate.test.ts | 2 +- .../author/proposal/__tests__/save.test.ts | 2 +- .../server/author/proposal/catalogChecks.ts | 2 +- .../server/author/proposal/fold.ts | 2 +- .../server/author/proposal/review.ts | 2 +- .../server/author/proposal/save.ts | 2 +- apps/agent-forge/server/author/provider.ts | 2 +- .../agent-forge/server/author/requirements.ts | 2 +- .../review/__tests__/reviewDraft.test.ts | 4 +- .../author/review/__tests__/state.test.ts | 2 +- .../server/author/review/reviewDraft.ts | 2 +- .../agent-forge/server/author/review/state.ts | 2 +- apps/agent-forge/server/author/services.ts | 4 +- .../tools/__tests__/condenseTrace.test.ts | 4 +- .../tools/__tests__/proposeAgent.test.ts | 4 +- .../__tests__/proposeAgentUpdate.test.ts | 4 +- .../author/tools/__tests__/readTools.test.ts | 2 +- .../tools/__tests__/requestSecret.test.ts | 2 +- .../author/tools/__tests__/runDraft.test.ts | 4 +- .../author/tools/__tests__/stateTools.test.ts | 4 +- .../server/author/tools/catalogService.ts | 2 +- .../server/author/tools/getTraceTool.ts | 2 +- apps/agent-forge/server/author/tools/index.ts | 2 +- .../server/author/tools/mcpProbeTool.ts | 2 +- .../server/author/tools/openPageTool.ts | 2 +- .../server/author/tools/proposeAgentTool.ts | 2 +- .../author/tools/proposeAgentUpdateTool.ts | 4 +- .../server/author/tools/readTools.ts | 4 +- .../server/author/tools/requestSecretTool.ts | 2 +- .../server/author/tools/runDraftTool.ts | 2 +- .../server/author/tools/saveTools.ts | 2 +- .../server/author/tools/searchDocsTool.ts | 2 +- .../server/author/tools/stateTools.ts | 4 +- apps/agent-forge/server/author/turnContext.ts | 2 +- apps/agent-forge/server/author/turnRunner.ts | 2 +- apps/agent-forge/server/author/wiring.ts | 4 +- apps/agent-forge/server/autoApproval.ts | 2 +- apps/agent-forge/server/buildAgent.test.ts | 2 +- apps/agent-forge/server/buildAgent.ts | 6 +- apps/agent-forge/server/buildAgentV2.ts | 4 +- apps/agent-forge/server/chatReconcile.ts | 2 +- apps/agent-forge/server/chatTriggerAdapter.ts | 6 +- apps/agent-forge/server/checkpointStore.ts | 2 +- apps/agent-forge/server/cli.ts | 142 ++ apps/agent-forge/server/collector.ts | 4 +- apps/agent-forge/server/compileHooks.test.ts | 4 +- apps/agent-forge/server/compileHooks.ts | 4 +- apps/agent-forge/server/debugController.ts | 2 +- .../server/diff/__tests__/specChange.test.ts | 2 +- apps/agent-forge/server/hookSandbox.test.ts | 2 +- apps/agent-forge/server/hookSandbox.ts | 2 +- apps/agent-forge/server/host/agentHost.ts | 2 +- .../server/host/bridgeAgentHost.ts | 2 +- apps/agent-forge/server/host/bridgeClient.ts | 2 +- apps/agent-forge/server/host/bridgeMapping.ts | 2 +- apps/agent-forge/server/host/hostSelector.ts | 498 +++---- .../server/host/inProcessAgentHost.ts | 2 +- apps/agent-forge/server/host/sdkCompat.ts | 20 +- apps/agent-forge/server/host/workspaceSdk.ts | 201 +-- .../inbox/__tests__/inboxAggregate.test.ts | 2 +- apps/agent-forge/server/index.ts | 45 +- apps/agent-forge/server/judge.ts | 4 +- .../lifecycle/__tests__/failureStreak.test.ts | 2 +- .../lifecycle/__tests__/publish.test.ts | 2 +- apps/agent-forge/server/lifecycle/copy.ts | 2 +- apps/agent-forge/server/lifecycle/export.ts | 2 +- apps/agent-forge/server/lifecycle/import.ts | 2 +- apps/agent-forge/server/lifecycle/publish.ts | 2 +- apps/agent-forge/server/logEntries.ts | 4 +- apps/agent-forge/server/nodeStates.ts | 2 +- apps/agent-forge/server/observeReporting.ts | 2 +- .../passes/__tests__/draftReview.test.ts | 2 +- .../server/passes/__tests__/passModel.test.ts | 4 +- .../passes/__tests__/passRunner.test.ts | 2 +- .../passes/__tests__/portability.test.ts | 2 +- .../__tests__/promptSuggestion.digest.test.ts | 2 +- .../passes/__tests__/promptSuggestion.test.ts | 4 +- .../passes/__tests__/versionSummary.test.ts | 2 +- apps/agent-forge/server/passes/draftReview.ts | 2 +- apps/agent-forge/server/passes/passModel.ts | 4 +- apps/agent-forge/server/passes/passRunner.ts | 4 +- apps/agent-forge/server/passes/portability.ts | 2 +- .../server/passes/portabilityRoutes.ts | 2 +- .../server/passes/promptSuggestion.ts | Bin 23198 -> 23222 bytes .../server/passes/versionSummary.ts | 2 +- apps/agent-forge/server/pins.ts | 2 +- apps/agent-forge/server/playground.ts | 6 +- .../routes/__tests__/agentListItems.test.ts | 2 +- .../server/routes/agentListItems.ts | 2 +- apps/agent-forge/server/routes/agentRoutes.ts | 2 +- .../server/routes/catalogRoutes.ts | 4 +- apps/agent-forge/server/routes/draftRoutes.ts | 2 +- apps/agent-forge/server/routes/mcpProbe.ts | 4 +- .../server/routes/versionRoutes.ts | 2 +- apps/agent-forge/server/runRegistry.ts | 10 +- apps/agent-forge/server/sdkRoot.ts | 51 +- .../server/storage/__tests__/agentIds.test.ts | 2 +- .../storage/__tests__/agentRepo.test.ts | 2 +- .../storage/__tests__/legacySpec.test.ts | 2 +- .../storage/__tests__/specFields.test.ts | 2 +- .../server/storage/__tests__/specHash.test.ts | 2 +- .../__tests__/versionStore.summary.test.ts | 2 +- .../storage/__tests__/versionStore.test.ts | 2 +- apps/agent-forge/server/storage/agentRepo.ts | 2 +- apps/agent-forge/server/storage/draftStore.ts | 2 +- apps/agent-forge/server/storage/legacySpec.ts | 2 +- apps/agent-forge/server/storage/specHash.ts | 2 +- .../server/templates/templateCatalog.ts | 2 +- apps/agent-forge/server/traceStore.ts | 2 +- .../server/triggers/abortListeners.ts | 4 +- .../server/triggers/chainTriggers.ts | 4 +- .../server/triggers/channelTriggers.ts | 6 +- .../server/triggers/cronTriggers.ts | 4 +- apps/agent-forge/server/triggers/runtime.ts | 4 +- .../server/triggers/triggerHost.ts | 4 +- .../server/triggers/webhookRoute.ts | 8 +- apps/agent-forge/server/tsup.config.ts | 6 +- .../workspace/__tests__/middleware.test.ts | 2 +- .../__tests__/workspaceManager.test.ts | 2 +- .../server/workspace/agentDirRoutes.ts | 2 +- apps/agent-forge/server/workspace/context.ts | 2 +- apps/agent-forge/shared/authorWire.ts | 2 +- apps/agent-forge/shared/nodeIds.ts | 2 +- apps/agent-forge/shared/specFields.ts | 2 +- apps/agent-forge/shared/wireTypes.ts | 2 +- .../canvas/kinds/flow/FlowInputsEditor.tsx | 2 +- .../src/canvas/kinds/flow/FlowMetaSection.tsx | 2 +- .../canvas/kinds/flow/RunOptionsSection.tsx | 2 +- apps/agent-forge/src/canvas/templates.ts | 2 +- .../src/components/debug/forkReplay.tsx | 2 +- apps/agent-forge/src/graph/v2/schemaIssues.ts | 2 +- apps/agent-forge/src/graph/v2/specTypes.ts | 40 +- .../agent-forge/src/persistence/AgentStore.ts | 2 +- .../src/persistence/HttpAgentStore.ts | 2 +- .../__tests__/HttpAgentStore.test.ts | 2 +- .../__tests__/fsAgentStore.test.ts | 2 +- .../__tests__/importExport.test.ts | 2 +- .../src/persistence/fsAgentStore.ts | 2 +- .../src/persistence/importExport.ts | 4 +- apps/agent-forge/src/runtime/runtimeClient.ts | 2 +- .../screens/agentDetail/AgentOverviewTab.tsx | 2 +- .../__tests__/AgentOverviewTab.test.tsx | 2 +- .../agentDetail/__tests__/DraftCard.test.tsx | 2 +- .../__tests__/SuggestionsTab.test.tsx | 2 +- .../__tests__/VersionsTab.test.tsx | 2 +- .../__tests__/overviewModel.test.ts | 2 +- .../src/screens/agentDetail/code/CodeTab.tsx | 2 +- .../code/__tests__/CodeTab.test.tsx | 4 +- .../agentDetail/configure/ConfigureTab.tsx | 2 +- .../configure/__tests__/ConfigureTab.test.tsx | 2 +- .../configure/__tests__/sections.test.ts | 2 +- .../screens/agentDetail/configure/sections.ts | 2 +- .../configure/useConfigureDraft.ts | 2 +- .../src/screens/agentDetail/overviewModel.ts | 2 +- .../runs/__tests__/RunsTab.test.tsx | 2 +- .../src/screens/codeAgent/CodeAgentScreen.tsx | 2 +- .../__tests__/CodeAgentCanvas.test.tsx | 2 +- .../__tests__/CodeAgentScreen.test.tsx | 2 +- .../screens/workspaces/OverviewSection.tsx | 4 +- .../src/screens/workspaces/PagesSection.tsx | 2 +- .../screens/workspaces/WorkspaceContext.tsx | 2 +- .../src/screens/workspaces/WorkspaceRoute.tsx | 2 +- .../screens/workspaces/WorkspacesScreen.tsx | 2 +- .../__tests__/WorkspaceRoute.test.tsx | 2 +- .../__tests__/WorkspacesScreen.test.tsx | 4 +- .../workspaces/__tests__/workspaceTestKit.tsx | 2 +- .../src/screens/workspaces/useClientQuery.ts | 2 +- .../src/screens/workspaces/useWorkspaceOps.ts | 2 +- .../src/screens/workspaces/workspaceTypes.ts | 2 +- .../screens/workspaces/workspacesClient.tsx | 2 +- apps/agent-forge/src/state/AppState.tsx | 2 +- .../state/history/__tests__/history.test.ts | 2 +- .../agent-forge/src/state/useAgentControls.ts | 2 +- .../agent-forge/src/state/useAgentDocument.ts | 2 +- apps/agent-forge/src/state/usePins.ts | 2 +- bin/lousho.js | 2 +- docs/acp.md | 8 +- docs/agent-directories.md | 24 +- docs/agent-forge.md | 18 +- docs/agent-spec.md | 14 +- docs/ai-sdk-ui.md | 4 +- docs/api-overview.md | 38 +- docs/approvals.md | 40 +- docs/auth.md | 46 +- docs/build-a-coding-agent.md | 18 +- docs/channels.md | 20 +- docs/claude-projects.md | 8 +- docs/cloudflare-workers.md | 30 +- docs/code-mode.md | 2 +- docs/compaction.md | 20 +- docs/configuration.md | 22 +- docs/connectors.md | 6 +- docs/decisions.md | 8 +- docs/deployment.md | 12 +- docs/durable-execution.md | 44 +- docs/errors.md | 12 +- docs/evals.md | 36 +- docs/executor-api.md | 4 +- docs/flows.md | 34 +- docs/guardrails.md | 12 +- docs/handoffs.md | 12 +- docs/hooks.md | 36 +- docs/hosted-tools.md | 12 +- docs/improve.md | 22 +- docs/installation.md | 54 +- docs/mcp.md | 28 +- docs/memory.md | 32 +- docs/migrating-from-ai-sdk.md | 10 +- docs/migrating-to-create-agent.md | 12 +- docs/models-and-cost.md | 8 +- docs/nextjs.md | 12 +- docs/oauth.md | 18 +- docs/observability.md | 38 +- docs/openapi-tools.md | 12 +- docs/pause-and-resume.md | 18 +- docs/permission-modes.md | 10 +- docs/prompting-techniques.md | 4 +- docs/providers.md | 36 +- docs/queue-and-steer.md | 8 +- docs/quick-start.md | 36 +- docs/react.md | 16 +- docs/reasoning.md | 2 +- docs/registry.md | 2 +- docs/runs.md | 8 +- docs/schedules.md | 10 +- docs/self-healing.md | 10 +- docs/sessions.md | 28 +- docs/skills.md | 4 +- docs/stream-events.md | 2 +- docs/streaming.md | 22 +- docs/structured-output.md | 28 +- docs/sub-agents.md | 30 +- docs/svelte.md | 8 +- docs/testing.md | 28 +- docs/tool-search.md | 6 +- docs/tools.md | 18 +- docs/triggers.md | 10 +- docs/troubleshooting.md | 8 +- docs/upgrading.md | 38 +- docs/utilities.md | 6 +- docs/vue.md | 8 +- docs/workspace-tools.md | 34 +- docs/workspaces.md | 18 +- examples/README.md | 2 +- examples/agent-dir/index.test.ts | 4 +- examples/agent-dir/index.ts | 4 +- examples/agent-dir/tools/word_count.ts | 2 +- examples/coding-agent-workflows/index.test.ts | 6 +- examples/coding-agent-workflows/index.ts | 8 +- examples/coding-harness/index.test.ts | 4 +- examples/coding-harness/index.ts | 4 +- examples/coding-harness/kit-pi.test.ts | 8 +- examples/coding-harness/kit.test.ts | 10 +- examples/connectors/index.test.ts | 4 +- examples/connectors/index.ts | 6 +- examples/connectors/mock-connectors-server.ts | 6 +- examples/data-analyst/index.test.ts | 2 +- examples/data-analyst/index.ts | 4 +- examples/deep-research/index.test.ts | 4 +- examples/deep-research/index.ts | 4 +- examples/doc-qa/index.ts | 6 +- examples/evaluator-loop/index.test.ts | 4 +- examples/evaluator-loop/index.ts | 4 +- examples/harnesses.live.test.ts | 2 +- examples/heal-benchmark/agents.ts | 2 +- examples/heal-benchmark/armSummary.test.ts | 2 +- examples/heal-benchmark/armSummary.ts | 2 +- examples/heal-benchmark/codingAgent.test.ts | 2 +- examples/heal-benchmark/codingAgent.ts | 2 +- examples/heal-benchmark/experiment.ts | 2 +- examples/heal-benchmark/gates.test.ts | 2 +- examples/heal-benchmark/gates.ts | 4 +- examples/heal-benchmark/improve-cli/agent.ts | 2 +- examples/heal-benchmark/improve-cli/check.ts | 2 +- .../improve-cli/improveCli.test.ts | 6 +- .../heal-benchmark/improve-cli/projectRuns.ts | 2 +- examples/heal-benchmark/improve-cli/record.ts | 4 +- examples/heal-benchmark/improve.ts | 4 +- examples/heal-benchmark/index.test.ts | 2 +- examples/heal-benchmark/index.ts | 4 +- examples/heal-benchmark/loop.test.ts | 2 +- examples/heal-benchmark/loop.ts | 2 +- examples/heal-benchmark/loopRuntime.ts | 8 +- examples/heal-benchmark/loopScenarios.ts | 2 +- examples/heal-benchmark/probe.ts | 6 +- examples/heal-benchmark/replay.test.ts | 4 +- examples/heal-benchmark/replay.ts | 2 +- examples/heal-benchmark/replayRows.ts | 2 +- examples/heal-benchmark/run.ts | 2 +- examples/heal-benchmark/simulate.ts | 2 +- .../hostinger-deploy/agent/tools/host.test.ts | 2 +- examples/hostinger-deploy/agent/tools/host.ts | 2 +- examples/imports.test.ts | 14 +- examples/openrouter/index.ts | 4 +- examples/ops-pipeline/demoProvider.ts | 4 +- examples/ops-pipeline/fixer.test.ts | 2 +- examples/ops-pipeline/fixer.ts | 10 +- examples/ops-pipeline/guardedPr.test.ts | 2 +- examples/ops-pipeline/guardedPr.ts | 4 +- examples/ops-pipeline/index.ts | 16 +- examples/ops-pipeline/mocks/mockGithubTool.ts | 2 +- examples/ops-pipeline/mocks/mockSlackTool.ts | 4 +- examples/ops-pipeline/monitor.test.ts | 4 +- examples/ops-pipeline/monitor.ts | 4 +- examples/ops-pipeline/pipeline.eval.ts | 8 +- .../ops-pipeline/slackInteractions.test.ts | 6 +- examples/ops-pipeline/slackInteractions.ts | 10 +- examples/phase-pipeline/index.test.ts | 6 +- examples/phase-pipeline/index.ts | 8 +- examples/plan-mode/index.ts | 8 +- examples/research-assistant/index.ts | 8 +- examples/slack-notifier/index.ts | 6 +- examples/support-bot/index.ts | 6 +- examples/support-desk/index.test.ts | 2 +- examples/support-desk/index.ts | 4 +- examples/tracing/console-exporter.ts | 2 +- examples/tracing/run-otel.ts | 2 +- examples/tracing/weather-scenario.ts | 10 +- examples/waves/index.test.ts | 4 +- examples/waves/index.ts | 4 +- examples/workflow-router/index.ts | 6 +- examples/workspace-marketing/host.ts | 6 +- examples/workspace-marketing/index.test.ts | 4 +- examples/workspace-marketing/run.ts | 4 +- llms-full.txt | 1248 ++++++++--------- llms.txt | 18 +- package-lock.json | 363 ++++- package.json | 2 +- packages/create-lousho/README.md | 2 +- packages/create-lousho/package.json | 2 +- packages/create-lousho/src/index.test.ts | 2 +- packages/create-lousho/src/index.ts | 2 +- .../create-lousho/src/template.e2e.test.ts | 6 +- packages/workspaces-react/README.md | 2 +- packages/workspaces-react/package.json | 2 +- .../workspaces-react/src/PageApp.test.tsx | 2 +- packages/workspaces-react/src/PageApp.tsx | 2 +- .../workspaces-react/src/__tests__/clients.ts | 2 +- .../src/components/actions.tsx | 2 +- .../src/components/inputs.tsx | 2 +- .../src/components/layout.tsx | 2 +- .../src/components/outputs.tsx | 2 +- .../src/components/status.tsx | 2 +- packages/workspaces-react/src/context.ts | 2 +- packages/workspaces-react/src/render.tsx | 2 +- packages/workspaces-react/src/visible.ts | 2 +- packages/workspaces-react/tsconfig.build.json | 4 +- packages/workspaces-react/tsconfig.json | 4 +- packages/workspaces-react/tsup.config.ts | 2 +- registry/coding-kit/approve.ts | 2 +- registry/coding-kit/hooks.ts | 2 +- registry/coding-kit/item.json | 2 +- .../subagents/explorer/tools/read-only.ts | 2 +- registry/coding-kit/tools/fs.ts | 2 +- registry/coding-pi/approve.ts | 2 +- registry/coding-pi/hooks.ts | 2 +- registry/coding-pi/item.json | 2 +- .../subagents/explorer/tools/read-only.ts | 2 +- registry/coding-pi/tools/fs.ts | 2 +- registry/dist/items/coding-kit.json | 10 +- registry/dist/items/coding-pi.json | 10 +- registry/dist/items/generic-webhook.json | 4 +- registry/dist/items/github-issues.json | 4 +- registry/dist/items/inbox-triage.json | 18 +- registry/dist/items/incident-response.json | 12 +- registry/dist/items/open-meteo-weather.json | 4 +- .../channels/generic-webhook.ts | 2 +- registry/generic-webhook/item.json | 2 +- registry/github-issues/item.json | 2 +- registry/github-issues/tools/github-issues.ts | 2 +- registry/inbox-triage/approve.ts | 2 +- registry/inbox-triage/channels/inbound.ts | 2 +- registry/inbox-triage/hooks.ts | 2 +- registry/inbox-triage/item.json | 2 +- registry/inbox-triage/kit.test.ts | 8 +- registry/inbox-triage/memory/senders.ts | 2 +- registry/inbox-triage/schedules/poll-inbox.ts | 2 +- registry/inbox-triage/tools/inbox.ts | 2 +- registry/inbox-triage/tools/replies.ts | 2 +- registry/incident-response/approve.ts | 2 +- registry/incident-response/channels/alerts.ts | 2 +- registry/incident-response/hooks.ts | 2 +- registry/incident-response/item.json | 2 +- registry/incident-response/kit.test.ts | 4 +- registry/incident-response/tools/infra.ts | 2 +- registry/kits.live.test.ts | 4 +- registry/open-meteo-weather/item.json | 2 +- .../tools/open-meteo-weather.ts | 2 +- scripts/publish-github-packages.mjs | 14 +- scripts/smokeKit.ts | 2 +- scripts/sync-version.mjs | 2 +- scripts/verify-docs-snippets.ts | 12 +- src/agentDir/collectTools.ts | 2 +- src/agentDir/dualPackage.test.ts | 12 +- src/agentDir/loadAgentDir.test.ts | 4 +- src/agentDir/loadAuth.ts | 2 +- src/agentDir/spec.ts | 2 +- src/auth/index.ts | 2 +- src/auth/routeAuth.test.ts | 2 +- src/channels/defineChannel.ts | 2 +- src/channels/discordChannel.ts | 2 +- src/channels/githubChannel.ts | 2 +- src/channels/httpChannel.ts | 2 +- src/channels/mountChannels.ts | 2 +- src/channels/slackChannel.ts | 2 +- src/channels/teamsChannel.ts | 2 +- src/channels/telegramChannel.ts | 2 +- src/channels/webhookChannel.ts | 2 +- src/claude/claudeProject.ts | 2 +- src/cli/addCheck.test.ts | 8 +- src/cli/defaultRegistry.test.ts | 4 +- src/cli/init.test.ts | 4 +- .../init/__snapshots__/templates.test.ts.snap | 44 +- src/cli/init/projectConfig.ts | 2 +- src/cli/init/sdkDependency.ts | 4 +- src/cli/init/telegramTemplate.ts | 6 +- src/cli/init/templates.test.ts | 6 +- src/cli/init/templates.ts | 12 +- src/cli/traces.test.ts | 2 +- src/cli/traces.ts | 2 +- src/createAgent.ts | 6 +- .../node-server-approvals/auth.ts | 4 +- .../tools/submit_expense.ts | 2 +- .../worker-agent-dir-full/channels/api.ts | 2 +- .../worker-agent-dir-full/memory/notes.ts | 2 +- .../worker-agent-dir-full/schedules/report.ts | 2 +- .../worker-agent-dir-full/tools/echo.ts | 2 +- .../worker-agent-dir/tools/echo.ts | 2 +- src/deploy/adapters/cloudflare-errors.test.ts | 4 +- src/deploy/adapters/cloudflare.dir.test.ts | 4 +- src/deploy/adapters/docker.test.ts | 2 +- src/deploy/adapters/node-server-dir.test.ts | 8 +- .../adapters/node-server-errors.test.ts | 2 +- .../adapters/node-server-module.test.ts | 2 +- .../adapters/node-server-providers.test.ts | 2 +- src/deploy/bundle.ts | 20 +- src/deploy/kv.test.ts | 4 +- src/deploy/kv.ts | 2 +- src/deploy/nodeServer.ts | 2 +- src/deploy/runtime.ts | 2 +- src/deploy/runtime.worker.ts | 2 +- src/deploy/shims/mcp.worker.ts | 2 +- src/deploy/worker.test.ts | 4 +- src/deploy/worker.ts | 4 +- src/deploy/workerMemory.ts | 2 +- src/deploy/workerSdk.ts | 2 +- src/deploy/workerSdkExports.ts | 4 +- src/execution/guardrailStarterSet.ts | 2 +- src/execution/index.ts | 8 +- src/execution/ioGuardrails.ts | 2 +- src/execution/otel.ts | 6 +- src/execution/permissions.ts | 6 +- src/index.ts | 12 +- src/integrations/index.ts | 2 +- src/oauth/defineOAuthProvider.ts | 2 +- src/observe/index.test.ts | 4 +- src/publicSurface.test-d.ts | 138 +- src/publicSurface.test.ts | 8 +- src/react/index.ts | 2 +- src/react/react.test-d.ts | 4 +- src/security/index.ts | 2 +- src/server/routeHandler.ts | 2 +- src/session/sessionId.ts | 2 +- .../__snapshots__/typescript.test.ts.snap | 2 +- src/spec/generators/typescript.test.ts | 4 +- src/spec/generators/typescript.ts | 4 +- src/spec/schema.ts | 4 +- src/spec/specToAgent.ts | 2 +- src/spec/v2/index.test.ts | 17 +- src/spec/v2/index.ts | 2 +- src/storage/sqlite/SqliteStore.ts | 2 +- src/storage/sqlite/driver.ts | 2 +- src/storage/sqlite/index.ts | 2 +- src/storage/sqlite/sqliteMemory.ts | 2 +- src/storage/sqlite/sqliteVectorMemory.ts | 2 +- src/subagents/piAgent.ts | 2 +- src/svelte/index.ts | 2 +- src/svelte/svelte.test-d.ts | 4 +- src/testing/cassette.ts | 2 +- src/testing/index.ts | 2 +- src/testing/mockModel.ts | 2 +- src/testing/recordReplay.ts | 2 +- src/tools/approvalPolicies.ts | 2 +- src/tools/built-in/index.ts | 2 +- src/tools/index.ts | 2 +- src/tools/rootTools.ts | 4 +- src/tools/workspace/MemoryWorkspace.ts | 2 +- src/tools/workspace/NodeWorkspace.ts | 2 +- src/tools/workspace/SandboxShell.ts | 2 +- src/tools/workspace/checkpointFileStore.ts | 2 +- src/tools/workspace/checkpoints.ts | 4 +- src/tools/workspace/fsTools.ts | 4 +- src/tools/workspace/shellTool.ts | 2 +- src/tools/workspace/types.ts | 4 +- src/traces/fileTraceExporter.ts | 4 +- src/traces/index.ts | 2 +- src/traces/readTraces.ts | 4 +- src/triggers/index.ts | 2 +- src/types/index.ts | 2 +- src/utils/index.ts | 2 +- src/vue/index.ts | 2 +- src/vue/vue.test-d.ts | 4 +- tsconfig.json | 52 +- tsup.config.ts | 2 +- vitest.config.ts | 18 +- 628 files changed, 4249 insertions(+), 3317 deletions(-) create mode 100644 apps/agent-forge/LICENSE create mode 100644 apps/agent-forge/README.md create mode 100644 apps/agent-forge/scripts/pin-sdk-range.mjs create mode 100644 apps/agent-forge/server/__tests__/cli.test.ts create mode 100644 apps/agent-forge/server/__tests__/pinSdkRange.test.ts create mode 100644 apps/agent-forge/server/__tests__/sdkRoot.test.ts create mode 100644 apps/agent-forge/server/cli.ts diff --git a/.fallowrc.json b/.fallowrc.json index 509c08b3a..8c3b17410 100644 --- a/.fallowrc.json +++ b/.fallowrc.json @@ -15,7 +15,10 @@ // The elkjs layout worker: only referenced through // `new Worker(new URL('./layout.worker.ts', import.meta.url))` in // apps/agent-forge/src/canvas/layout.ts (C17). - "apps/agent-forge/src/canvas/layout.worker.ts" + "apps/agent-forge/src/canvas/layout.worker.ts", + // The packaged Studio's `lousho-studio` bin and its pack-time SDK-range pin. + "apps/agent-forge/server/cli.ts", + "apps/agent-forge/scripts/pin-sdk-range.mjs" ], "ignorePatterns": [ ".claude/**" @@ -96,7 +99,7 @@ "@ai-sdk/anthropic", "@ai-sdk/google", "@ai-sdk/openai", - "lousho", + "@linuxdevil/lousho", "ollama-ai-provider", "ollama-ai-provider-v2", "prompts" diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index f50328cd3..8b0099ab1 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -31,7 +31,7 @@ What did you run, and what did it show? Name anything you couldn't verify, such ## Release -Merging doesn't publish. The Release workflow publishes `lousho` and `create-lousho` to npm as `latest` every Monday at 06:00 UTC, or when run by hand, so everyone on a default install gets this change in the next release. The `Unreleased` entry above becomes that release's notes. +Merging publishes `@linuxdevil/lousho` and `create-lousho` to npm as `latest`, so everyone on a default install gets this change. - [ ] This PR doesn't need a release on its own, so I added the `norelease` label. The weekly run skips when every PR since the last release has it diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4a15e953d..b36a6f4a3 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -51,7 +51,7 @@ jobs: - run: npm run registry:check # schemas/ is generated from the workspace contracts; fail if stale. - run: npm run workspaces:schema:check - # Agent Forge imports the SDK as `lousho` via `file:../..`, i.e. the root dist/ built above. + # Agent Forge imports the SDK as `@linuxdevil/lousho` via `file:../..`, i.e. the root dist/ built above. - run: npm run typecheck --workspace apps/agent-forge - run: npm run typecheck:server --workspace apps/agent-forge zod4: diff --git a/.gitignore b/.gitignore index 12480d960..dd7bc06eb 100644 --- a/.gitignore +++ b/.gitignore @@ -81,3 +81,6 @@ snip.txt # Author eval run outputs (npm run eval:author --workspace apps/agent-forge). # baseline.json is committed and lives one level up at evals/author/. apps/agent-forge/evals/author/results/*.json + +# Studio pack-time SDK-range pin backup +.package.json.prepack diff --git a/CHANGELOG.md b/CHANGELOG.md index 9cf81d763..e916afd35 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,7 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] -- New `lousho/observe` subpath: `startObserveDaemon`, `probeHealth`, `isObserveDaemon`, `defaultDbPath`, `OBSERVE_DEFAULT_PORT` and the daemon option/retention defaults, so a host process (Studio) can run and probe the collector through public exports. +- **Breaking:** the package is renamed from `lousho` to `@linuxdevil/lousho`. Update imports from `'lousho'` / `'lousho/…'` to `'@linuxdevil/lousho'` / `'@linuxdevil/lousho/…'` and dependencies from `"lousho"` to `"@linuxdevil/lousho"`. The `lousho` binary name is unchanged, so `npx @linuxdevil/lousho …` and `lousho …` commands keep working. +- New `@linuxdevil/lousho/observe` subpath: `startObserveDaemon`, `probeHealth`, `isObserveDaemon`, `defaultDbPath`, `OBSERVE_DEFAULT_PORT` and the daemon option/retention defaults, so a host process (Studio) can run and probe the collector through public exports. +- Install docs use the public npm packages again (`npm install @linuxdevil/lousho`, `npm create lousho`). +- `@linuxdevil/lousho/improve` (experimental): `improveAgent()` proposes tested edits to an agent's instructions and skills, offline, gated against the current harness and held-out cases. It applies nothing. See [Improving an agent](docs/improve.md). - `lousho bridge`: a new command that speaks Studio's bridge protocol (NDJSON on stdin/stdout, protocol 1) so Lousho Studio can build and run a project's agents with that project's own SDK and dependencies. It answers `hello`, `catalog`, `compile` and `shutdown`, and runs agents: `run`/`chat` start a run or chat turn, `steer`/`abort` control it, `resolveApproval`/`answerQuestion` resume a paused run, and every `AgentEvent`, trace span, steer join and run settle arrives as a pushed event (flows are refused with `LOUSHO_HOST_UNSUPPORTED`). - Model strings resolve any official AI SDK provider: `mistral/...`, `groq/...`, `xai/...`, `bedrock/...`, `vertex/...`, `azure/...` and 13 more load `@ai-sdk/` on the first call, and `ai-sdk//` loads any other `@ai-sdk/*` package. The package reads its own credentials. See [Providers](docs/providers.md#any-ai-sdk-vendor-by-name). diff --git a/README.md b/README.md index 58a5231cf..2f2434f80 100644 --- a/README.md +++ b/README.md @@ -55,8 +55,8 @@ npm test Now add an eval. Save this as `src/weather.eval.ts`: ```ts no-run -import { createAgent, defineEval, defineTool } from 'lousho'; -import { mockModel } from 'lousho/testing'; +import { createAgent, defineEval, defineTool } from '@linuxdevil/lousho'; +import { mockModel } from '@linuxdevil/lousho/testing'; import { z } from 'zod'; const getWeather = defineTool({ @@ -138,7 +138,7 @@ See [Installation](https://lousho.com/installation#provider-packages) for other This file uses top-level `await`, so it must be an ES module (`.mts`, or `"type": "module"` in `package.json`). Set `OPENAI_API_KEY` to run it. ```ts -import { createAgent, defineTool } from 'lousho'; +import { createAgent, defineTool } from '@linuxdevil/lousho'; import { z } from 'zod'; const getWeather = defineTool({ diff --git a/api/auth.api.md b/api/auth.api.md index 6f180cee0..af597439d 100644 --- a/api/auth.api.md +++ b/api/auth.api.md @@ -1,4 +1,4 @@ -## API Report File for "lousho" +## API Report File for "@linuxdevil/lousho" > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). diff --git a/api/executor.api.md b/api/executor.api.md index 44cab4d9c..25e2412c4 100644 --- a/api/executor.api.md +++ b/api/executor.api.md @@ -1,4 +1,4 @@ -## API Report File for "lousho" +## API Report File for "@linuxdevil/lousho" > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). diff --git a/api/flows.api.md b/api/flows.api.md index ab6664d6b..57a5ca7a4 100644 --- a/api/flows.api.md +++ b/api/flows.api.md @@ -1,4 +1,4 @@ -## API Report File for "lousho" +## API Report File for "@linuxdevil/lousho" > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). diff --git a/api/hooks.api.md b/api/hooks.api.md index 5e6146d6c..9b0ba4138 100644 --- a/api/hooks.api.md +++ b/api/hooks.api.md @@ -1,4 +1,4 @@ -## API Report File for "lousho" +## API Report File for "@linuxdevil/lousho" > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). diff --git a/api/improve.api.md b/api/improve.api.md index a6b5dbe10..f66bf32a5 100644 --- a/api/improve.api.md +++ b/api/improve.api.md @@ -1,4 +1,4 @@ -## API Report File for "lousho" +## API Report File for "@linuxdevil/lousho" > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). diff --git a/api/index.api.md b/api/index.api.md index 02f588a2c..a657c610c 100644 --- a/api/index.api.md +++ b/api/index.api.md @@ -1,4 +1,4 @@ -## API Report File for "lousho" +## API Report File for "@linuxdevil/lousho" > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). diff --git a/api/integrations.api.md b/api/integrations.api.md index a3feca042..bf30135e4 100644 --- a/api/integrations.api.md +++ b/api/integrations.api.md @@ -1,4 +1,4 @@ -## API Report File for "lousho" +## API Report File for "@linuxdevil/lousho" > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). diff --git a/api/kv.api.md b/api/kv.api.md index a7ec21753..de19a692e 100644 --- a/api/kv.api.md +++ b/api/kv.api.md @@ -1,4 +1,4 @@ -## API Report File for "lousho" +## API Report File for "@linuxdevil/lousho" > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). diff --git a/api/mcp.api.md b/api/mcp.api.md index 18792b86e..6dbc96985 100644 --- a/api/mcp.api.md +++ b/api/mcp.api.md @@ -1,4 +1,4 @@ -## API Report File for "lousho" +## API Report File for "@linuxdevil/lousho" > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). diff --git a/api/observe.api.md b/api/observe.api.md index 5637206d7..2e8fcbed7 100644 --- a/api/observe.api.md +++ b/api/observe.api.md @@ -1,4 +1,4 @@ -## API Report File for "lousho" +## API Report File for "@linuxdevil/lousho" > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). diff --git a/api/otel.api.md b/api/otel.api.md index 54669478c..752e96d11 100644 --- a/api/otel.api.md +++ b/api/otel.api.md @@ -1,4 +1,4 @@ -## API Report File for "lousho" +## API Report File for "@linuxdevil/lousho" > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). diff --git a/api/react.api.md b/api/react.api.md index b130de141..ba9bc1d16 100644 --- a/api/react.api.md +++ b/api/react.api.md @@ -1,4 +1,4 @@ -## API Report File for "lousho" +## API Report File for "@linuxdevil/lousho" > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). diff --git a/api/spec.api.md b/api/spec.api.md index acebe547d..8b14d90e1 100644 --- a/api/spec.api.md +++ b/api/spec.api.md @@ -1,4 +1,4 @@ -## API Report File for "lousho" +## API Report File for "@linuxdevil/lousho" > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). diff --git a/api/sqlite.api.md b/api/sqlite.api.md index 31c7cee08..57dbf154c 100644 --- a/api/sqlite.api.md +++ b/api/sqlite.api.md @@ -1,4 +1,4 @@ -## API Report File for "lousho" +## API Report File for "@linuxdevil/lousho" > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). diff --git a/api/svelte.api.md b/api/svelte.api.md index 84c7ab500..d3e1a4df2 100644 --- a/api/svelte.api.md +++ b/api/svelte.api.md @@ -1,4 +1,4 @@ -## API Report File for "lousho" +## API Report File for "@linuxdevil/lousho" > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). diff --git a/api/testing.api.md b/api/testing.api.md index ecd855c9f..e7c7308df 100644 --- a/api/testing.api.md +++ b/api/testing.api.md @@ -1,4 +1,4 @@ -## API Report File for "lousho" +## API Report File for "@linuxdevil/lousho" > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). diff --git a/api/tools.api.md b/api/tools.api.md index 6d0c7688d..6a9c4d76d 100644 --- a/api/tools.api.md +++ b/api/tools.api.md @@ -1,4 +1,4 @@ -## API Report File for "lousho" +## API Report File for "@linuxdevil/lousho" > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). diff --git a/api/traces.api.md b/api/traces.api.md index 60551ab1d..e2edd096e 100644 --- a/api/traces.api.md +++ b/api/traces.api.md @@ -1,4 +1,4 @@ -## API Report File for "lousho" +## API Report File for "@linuxdevil/lousho" > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). diff --git a/api/triggers.api.md b/api/triggers.api.md index 702b2d30a..920ffd7c5 100644 --- a/api/triggers.api.md +++ b/api/triggers.api.md @@ -1,4 +1,4 @@ -## API Report File for "lousho" +## API Report File for "@linuxdevil/lousho" > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). diff --git a/api/types.api.md b/api/types.api.md index 0438a9224..ee0f4b6fd 100644 --- a/api/types.api.md +++ b/api/types.api.md @@ -1,4 +1,4 @@ -## API Report File for "lousho" +## API Report File for "@linuxdevil/lousho" > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). diff --git a/api/utils.api.md b/api/utils.api.md index 6285152f3..10d3b5f9f 100644 --- a/api/utils.api.md +++ b/api/utils.api.md @@ -1,4 +1,4 @@ -## API Report File for "lousho" +## API Report File for "@linuxdevil/lousho" > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). diff --git a/api/vue.api.md b/api/vue.api.md index b2ddc23f0..c653cef6e 100644 --- a/api/vue.api.md +++ b/api/vue.api.md @@ -1,4 +1,4 @@ -## API Report File for "lousho" +## API Report File for "@linuxdevil/lousho" > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). diff --git a/api/worker.api.md b/api/worker.api.md index bf1baa62b..ab036a1c9 100644 --- a/api/worker.api.md +++ b/api/worker.api.md @@ -1,4 +1,4 @@ -## API Report File for "lousho" +## API Report File for "@linuxdevil/lousho" > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). diff --git a/apps/agent-forge/LICENSE b/apps/agent-forge/LICENSE new file mode 100644 index 000000000..06c4dd43a --- /dev/null +++ b/apps/agent-forge/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2025 Ali Mohammad + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/apps/agent-forge/README.md b/apps/agent-forge/README.md new file mode 100644 index 000000000..66015c12c --- /dev/null +++ b/apps/agent-forge/README.md @@ -0,0 +1,53 @@ +# Lousho Studio + +A local web app for building, testing and watching every agent across your +projects — powered by [`@linuxdevil/lousho`](https://www.npmjs.com/package/@linuxdevil/lousho). + +## Install & run + +```sh +npx @linuxdevil/studio # open Studio on the current folder +npx @linuxdevil/studio --workspace ~/code/my-app --port 5000 +``` + +Or install it globally and use the `lousho-studio` command: + +```sh +npm i -g @linuxdevil/studio +lousho-studio --workspace ~/code/my-app +``` + +If your project already has the SDK, `lousho studio` (from +`@linuxdevil/lousho`) launches the same app. + +Studio prints a URL with a per-launch token — every API and WebSocket +request needs it, so keep the URL private to your machine (it binds to +`127.0.0.1` by default; pass `--host` to change that). + +## Workspaces + +A **workspace** is a project folder. Studio reads and writes each workspace's +agents under its `.lousho/` directory, so one Studio instance can follow many +projects. `--workspace ` opens that folder and adds it to your workspace +list; the first run uses the current folder. + +Runs use the project's own SDK when the workspace has `@linuxdevil/lousho` +installed, so Studio works across SDK versions. See +[Agent Forge](https://lousho.com/agent-forge) for the full guide. + +## Options + +``` +Usage: lousho-studio [--workspace ] [--port N] [--host H] + + --workspace a project folder to open (default: the current folder) + --port N the port (default 4750) + --host H the interface to listen on (default 127.0.0.1) + -v, --version print the version + -h, --help show this help +``` + +## Requirements + +Node.js >= 22.19. Provider API keys are configured inside Studio's Settings; +no keys are needed to open the app. diff --git a/apps/agent-forge/eslintRules/noSdkSource.mjs b/apps/agent-forge/eslintRules/noSdkSource.mjs index 34fc9e592..82c22266f 100644 --- a/apps/agent-forge/eslintRules/noSdkSource.mjs +++ b/apps/agent-forge/eslintRules/noSdkSource.mjs @@ -1,48 +1,48 @@ -/** Studio must import the SDK through its package exports, never its files (PLAN §19.7). */ -import path from 'node:path'; -import { fileURLToPath } from 'node:url'; - -const APP_ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..'); - -/** `@lousho/build-ai-agent` after the rename, `lousho` until W13 lands. */ -const SDK_DEEP_IMPORT = /^(?:@lousho\/build-ai-agent|lousho)\/(?:src|dist)(?:\/|$)/; - -function isOutsideAppRoot(filename, specifier) { - if (!specifier.startsWith('.')) return false; - const resolved = path.resolve(path.dirname(filename), specifier); - return path.relative(APP_ROOT, resolved).startsWith('..'); -} - -export const noSdkSource = { - meta: { - type: 'problem', - docs: { description: 'Forbid importing SDK source files; use package exports only.' }, - messages: { deep: "Import the SDK as '@lousho/build-ai-agent' or a public subpath, not '{{source}}'." }, - schema: [], - }, - create(context) { - const filename = context.filename; - const check = (node, specifier) => { - if (typeof specifier !== 'string') return; - if (SDK_DEEP_IMPORT.test(specifier) || isOutsideAppRoot(filename, specifier)) { - context.report({ node, messageId: 'deep', data: { source: specifier } }); - } - }; - const sourceOf = (node) => (typeof node?.source?.value === 'string' ? node.source.value : undefined); - return { - ImportDeclaration: (node) => check(node, sourceOf(node)), - ExportNamedDeclaration: (node) => check(node, sourceOf(node)), - ExportAllDeclaration: (node) => check(node, sourceOf(node)), - ImportExpression(node) { - if (node.source?.type === 'Literal' && typeof node.source.value === 'string') check(node.source, node.source.value); - }, - CallExpression(node) { - const callee = node.callee; - if (callee?.type !== 'MemberExpression' || callee.object?.type !== 'Identifier' || callee.object.name !== 'vi') return; - if (callee.property?.type !== 'Identifier' || !['mock', 'importActual', 'importMock'].includes(callee.property.name)) return; - const first = node.arguments[0]; - if (first?.type === 'Literal' && typeof first.value === 'string') check(first, first.value); - }, - }; - }, -}; +/** Studio must import the SDK through its package exports, never its files (PLAN §19.7). */ +import path from 'node:path'; +import { fileURLToPath } from 'node:url'; + +const APP_ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..'); + +/** The published names the rule guards: `@linuxdevil/lousho` now, `lousho`/`@lousho/build-ai-agent` for older layouts. */ +const SDK_DEEP_IMPORT = /^(?:@linuxdevil\/lousho|@lousho\/build-ai-agent|lousho)\/(?:src|dist)(?:\/|$)/; + +function isOutsideAppRoot(filename, specifier) { + if (!specifier.startsWith('.')) return false; + const resolved = path.resolve(path.dirname(filename), specifier); + return path.relative(APP_ROOT, resolved).startsWith('..'); +} + +export const noSdkSource = { + meta: { + type: 'problem', + docs: { description: 'Forbid importing SDK source files; use package exports only.' }, + messages: { deep: "Import the SDK as '@linuxdevil/lousho' or a public subpath, not '{{source}}'." }, + schema: [], + }, + create(context) { + const filename = context.filename; + const check = (node, specifier) => { + if (typeof specifier !== 'string') return; + if (SDK_DEEP_IMPORT.test(specifier) || isOutsideAppRoot(filename, specifier)) { + context.report({ node, messageId: 'deep', data: { source: specifier } }); + } + }; + const sourceOf = (node) => (typeof node?.source?.value === 'string' ? node.source.value : undefined); + return { + ImportDeclaration: (node) => check(node, sourceOf(node)), + ExportNamedDeclaration: (node) => check(node, sourceOf(node)), + ExportAllDeclaration: (node) => check(node, sourceOf(node)), + ImportExpression(node) { + if (node.source?.type === 'Literal' && typeof node.source.value === 'string') check(node.source, node.source.value); + }, + CallExpression(node) { + const callee = node.callee; + if (callee?.type !== 'MemberExpression' || callee.object?.type !== 'Identifier' || callee.object.name !== 'vi') return; + if (callee.property?.type !== 'Identifier' || !['mock', 'importActual', 'importMock'].includes(callee.property.name)) return; + const first = node.arguments[0]; + if (first?.type === 'Literal' && typeof first.value === 'string') check(first, first.value); + }, + }; + }, +}; diff --git a/apps/agent-forge/evals/author/harness.ts b/apps/agent-forge/evals/author/harness.ts index d9990d2e0..2c77a28d8 100644 --- a/apps/agent-forge/evals/author/harness.ts +++ b/apps/agent-forge/evals/author/harness.ts @@ -15,9 +15,9 @@ import * as os from 'node:os'; import * as path from 'node:path'; import { randomBytes } from 'node:crypto'; import { parse as parseYaml } from 'yaml'; -import { agentSpecV2Schema, upgradeSpec, VERSION, type LLMProvider } from 'lousho'; +import { agentSpecV2Schema, upgradeSpec, VERSION, type LLMProvider } from '@linuxdevil/lousho'; import { envCollector } from '../../server/host/hostSelector'; -import { mockModel, type MockRequest, type MockStaticTurn, type MockToolCall } from 'lousho/testing'; +import { mockModel, type MockRequest, type MockStaticTurn, type MockToolCall } from '@linuxdevil/lousho/testing'; import type { AuthorMessage, AuthorServerEvent } from '../../shared/authorWire'; import { SecretsStore } from '../../server/secretsStore'; import { SettingsStore } from '../../server/settingsStore'; diff --git a/apps/agent-forge/evals/author/judge.ts b/apps/agent-forge/evals/author/judge.ts index 0ae736a1f..423547d0e 100644 --- a/apps/agent-forge/evals/author/judge.ts +++ b/apps/agent-forge/evals/author/judge.ts @@ -6,7 +6,7 @@ * or tool input in the transcript zeroes the score, so the judge cannot * pass a scenario on invented evidence. */ -import { createAgent, type LLMProvider } from 'lousho'; +import { createAgent, type LLMProvider } from '@linuxdevil/lousho'; import { z } from 'zod'; import type { AuthorMessage } from '../../shared/authorWire'; import { userMessageText } from '../../server/author/modelMessages'; diff --git a/apps/agent-forge/evals/author/run.ts b/apps/agent-forge/evals/author/run.ts index 5affe162c..f8242807f 100644 --- a/apps/agent-forge/evals/author/run.ts +++ b/apps/agent-forge/evals/author/run.ts @@ -19,7 +19,7 @@ import * as fs from 'node:fs'; import * as path from 'node:path'; import { fileURLToPath } from 'node:url'; -import type { LLMProvider } from 'lousho'; +import type { LLMProvider } from '@linuxdevil/lousho'; import type { AuthorMessage, AuthorSettings } from '../../shared/authorWire'; import { SecretsStore } from '../../server/secretsStore'; import { passReview } from '../../server/author/proposal/review'; diff --git a/apps/agent-forge/package.json b/apps/agent-forge/package.json index 93884237b..59362f18c 100644 --- a/apps/agent-forge/package.json +++ b/apps/agent-forge/package.json @@ -1,9 +1,31 @@ { - "name": "@lousho/agent-forge", - "version": "0.0.1", - "private": true, - "description": "Agent Forge - visual dashboard for creating, running and debugging agents built with lousho (app shell & data model).", + "name": "@linuxdevil/studio", + "version": "0.1.0-alpha.0", + "description": "Lousho Studio: build, test and watch every agent across your projects. A local web app for @linuxdevil/lousho.", "type": "module", + "bin": { + "lousho-studio": "dist-server/cli.cjs" + }, + "files": [ + "dist", + "dist-server", + "!dist-server/*.map", + "README.md", + "LICENSE" + ], + "engines": { + "node": ">=22.19.0" + }, + "license": "MIT", + "repository": { + "type": "git", + "url": "git+https://github.com/LinuxDevil/agent-sdk.git", + "directory": "apps/agent-forge" + }, + "homepage": "https://lousho.com/introduction", + "publishConfig": { + "access": "public" + }, "scripts": { "dev": "vite", "build": "tsc --noEmit -p tsconfig.json && vite build", @@ -17,16 +39,21 @@ "server:build": "tsup --config server/tsup.config.ts", "server:start:prod": "node dist-server/index.cjs", "build:studio": "npm run build && npm run server:build", + "prepack": "node scripts/pin-sdk-range.mjs pin", + "postpack": "node scripts/pin-sdk-range.mjs restore", "test:server": "vitest run -c server/vitest.config.ts", "typecheck:server": "tsc --noEmit -p server/tsconfig.json && tsc --noEmit -p evals/tsconfig.json", "eval:author": "tsx evals/author/run.ts", "test:e2e:studio": "npm run build:studio && playwright test" }, "dependencies": { + "@ai-sdk/anthropic": "^4.0.0", + "@ai-sdk/openai": "^4.0.0", "@codemirror/lang-javascript": "^6.2.5", - "lousho": "file:../..", + "@linuxdevil/lousho": "file:../..", "@uiw/react-codemirror": "^4.25.12", "@xyflow/react": "^12.12.0", + "ai": "^7.0.0", "elkjs": "^0.9.3", "express": "^5.2.1", "react": "^18.3.1", @@ -36,9 +63,8 @@ "zod": "^3.25.76" }, "devDependencies": { - "@ai-sdk/anthropic": "^0.0.42", - "@ai-sdk/openai": "^0.0.42", "@axe-core/playwright": "4.13.0", + "@codemirror/lang-javascript": "^6.2.5", "@fontsource-variable/inter": "^5.3.0", "@playwright/test": "^1.63.0", "@types/express": "^5.0.5", @@ -46,17 +72,24 @@ "@types/react-dom": "^18.3.1", "@types/supertest": "^6.0.3", "@types/ws": "^8.18.1", - "diff": "^8.0.4", + "@uiw/react-codemirror": "^4.25.12", "@vitejs/plugin-react": "^4.3.4", + "@xyflow/react": "^12.12.0", + "dagre": "^0.8.5", + "diff": "^8.0.4", + "express": "^5.2.1", "jsdom": "^25.0.1", "lucide-react": "^1.63.0", "ollama-ai-provider": "^1.2.0", + "react": "^18.3.1", + "react-dom": "^18.3.1", "supertest": "^7.3.0", "tsup": "^8.0.0", "tsx": "^4.23.15", "typescript": "^5.9.3", "typescript-eslint": "^8.71.0", "vite": "^5.4.11", - "vitest": "^1.0.0" + "vitest": "^1.0.0", + "ws": "^8.18.3" } } diff --git a/apps/agent-forge/scripts/pin-sdk-range.mjs b/apps/agent-forge/scripts/pin-sdk-range.mjs new file mode 100644 index 000000000..2af900346 --- /dev/null +++ b/apps/agent-forge/scripts/pin-sdk-range.mjs @@ -0,0 +1,59 @@ +#!/usr/bin/env node +/** + * Rewrites the Studio package's SDK dependency for packing. + * + * In the monorepo `dependencies['@linuxdevil/lousho']` stays `file:../..` + * so the dev loop always runs the checkout next door. `npm pack`/`publish` + * run `prepack` (`pin`) first: when the repo root package is the SDK + * (`@linuxdevil/lousho`), the range is rewritten to `^` and the + * original file is kept as `.package.json.prepack`; `postpack` (`restore`) + * puts it back. Outside that layout `pin` is a no-op. + * + * Usage: node scripts/pin-sdk-range.mjs pin|restore (cwd: apps/agent-forge) + */ +import * as fs from 'node:fs'; +import * as path from 'node:path'; + +const SDK_PACKAGE = '@linuxdevil/lousho'; +const DEP_KEY = SDK_PACKAGE; +const BACKUP = '.package.json.prepack'; + +const appDir = process.cwd(); +const pkgPath = path.join(appDir, 'package.json'); +const rootPkgPath = path.join(appDir, '..', '..', 'package.json'); + +function readJson(file) { + return JSON.parse(fs.readFileSync(file, 'utf8')); +} + +function pin() { + const backupPath = path.join(appDir, BACKUP); + if (fs.existsSync(backupPath)) { + console.error('pin-sdk-range: restore first (.package.json.prepack already exists)'); + process.exit(1); + } + if (!fs.existsSync(rootPkgPath) || !fs.existsSync(pkgPath)) return; + const root = readJson(rootPkgPath); + if (root.name !== SDK_PACKAGE) return; + const pkg = readJson(pkgPath); + if (pkg.dependencies?.[DEP_KEY] === undefined) return; + fs.writeFileSync(backupPath, fs.readFileSync(pkgPath)); + pkg.dependencies[DEP_KEY] = `^${root.version}`; + fs.writeFileSync(pkgPath, `${JSON.stringify(pkg, null, 2)}\n`); + console.log(`pin-sdk-range: ${DEP_KEY} -> ^${root.version}`); +} + +function restore() { + const backupPath = path.join(appDir, BACKUP); + if (!fs.existsSync(backupPath)) return; + fs.renameSync(backupPath, pkgPath); + console.log('pin-sdk-range: restored package.json'); +} + +const command = process.argv[2]; +if (command === 'pin') pin(); +else if (command === 'restore') restore(); +else { + console.error('pin-sdk-range: usage: pin-sdk-range.mjs pin|restore'); + process.exit(2); +} diff --git a/apps/agent-forge/server/__tests__/abort.test.ts b/apps/agent-forge/server/__tests__/abort.test.ts index 638d7ed5a..d74933c98 100644 --- a/apps/agent-forge/server/__tests__/abort.test.ts +++ b/apps/agent-forge/server/__tests__/abort.test.ts @@ -29,7 +29,7 @@ import { type AgentSpecV2, type GenerateOptions, type LLMProvider, -} from 'lousho'; +} from '@linuxdevil/lousho'; import { createApp } from '../app'; import { RunManager } from '../runRegistry'; import { newTestHost } from './testHost'; diff --git a/apps/agent-forge/server/__tests__/agentRequirements.test.ts b/apps/agent-forge/server/__tests__/agentRequirements.test.ts index f7d1819cc..9b204bf4b 100644 --- a/apps/agent-forge/server/__tests__/agentRequirements.test.ts +++ b/apps/agent-forge/server/__tests__/agentRequirements.test.ts @@ -9,7 +9,7 @@ import * as fs from 'node:fs'; import * as os from 'node:os'; import * as path from 'node:path'; import request from 'supertest'; -import type { AgentSpecV2 } from 'lousho'; +import type { AgentSpecV2 } from '@linuxdevil/lousho'; import { createApp } from '../app'; import { RunManager } from '../runRegistry'; import { newTestHost } from './testHost'; diff --git a/apps/agent-forge/server/__tests__/agentRoutes.create.test.ts b/apps/agent-forge/server/__tests__/agentRoutes.create.test.ts index d9b6ec68e..b5f2fc259 100644 --- a/apps/agent-forge/server/__tests__/agentRoutes.create.test.ts +++ b/apps/agent-forge/server/__tests__/agentRoutes.create.test.ts @@ -8,7 +8,7 @@ import * as fs from 'node:fs'; import * as os from 'node:os'; import * as path from 'node:path'; import request from 'supertest'; -import type { AgentSpecV2 } from 'lousho'; +import type { AgentSpecV2 } from '@linuxdevil/lousho'; import { createApp } from '../app'; import { RunManager } from '../runRegistry'; import { newTestHost } from './testHost'; diff --git a/apps/agent-forge/server/__tests__/agentRoutes.test.ts b/apps/agent-forge/server/__tests__/agentRoutes.test.ts index 7e3277fd5..e59aa51c7 100644 --- a/apps/agent-forge/server/__tests__/agentRoutes.test.ts +++ b/apps/agent-forge/server/__tests__/agentRoutes.test.ts @@ -10,7 +10,7 @@ import * as os from 'node:os'; import * as path from 'node:path'; import request from 'supertest'; import { parse as parseYaml } from 'yaml'; -import type { AgentSpec, AgentSpecV2 } from 'lousho'; +import type { AgentSpec, AgentSpecV2 } from '@linuxdevil/lousho'; import { createApp } from '../app'; import { RunManager } from '../runRegistry'; import { newTestHost } from './testHost'; diff --git a/apps/agent-forge/server/__tests__/allAgentsRoute.test.ts b/apps/agent-forge/server/__tests__/allAgentsRoute.test.ts index 27b783039..1939b4f01 100644 --- a/apps/agent-forge/server/__tests__/allAgentsRoute.test.ts +++ b/apps/agent-forge/server/__tests__/allAgentsRoute.test.ts @@ -4,7 +4,7 @@ import * as fs from 'node:fs'; import * as os from 'node:os'; import * as path from 'node:path'; import request from 'supertest'; -import { VERSION, type AgentSpecV2 } from 'lousho'; +import { VERSION, type AgentSpecV2 } from '@linuxdevil/lousho'; import { createApp } from '../app'; import type { Express } from 'express'; import { createWorkspaceContext, type WorkspaceContext, type WorkspaceServices } from '../workspace/context'; diff --git a/apps/agent-forge/server/__tests__/app.test.ts b/apps/agent-forge/server/__tests__/app.test.ts index 12189da38..ef7b47649 100644 --- a/apps/agent-forge/server/__tests__/app.test.ts +++ b/apps/agent-forge/server/__tests__/app.test.ts @@ -3,7 +3,7 @@ import * as fs from 'node:fs'; import * as path from 'node:path'; import * as os from 'node:os'; import request from 'supertest'; -import type { AgentSpec, AgentSpecV2 } from 'lousho'; +import type { AgentSpec, AgentSpecV2 } from '@linuxdevil/lousho'; import { createApp } from '../app'; import { RunManager } from '../runRegistry'; import { newTestHost } from './testHost'; diff --git a/apps/agent-forge/server/__tests__/approvalFlow.test.ts b/apps/agent-forge/server/__tests__/approvalFlow.test.ts index 5d7b46416..380d08a88 100644 --- a/apps/agent-forge/server/__tests__/approvalFlow.test.ts +++ b/apps/agent-forge/server/__tests__/approvalFlow.test.ts @@ -18,8 +18,8 @@ import { describe, it, expect, beforeEach, afterEach } from 'vitest'; import * as fs from 'node:fs'; import * as path from 'node:path'; import * as os from 'node:os'; -import { AgentExecutor, ToolRegistry, AgentBuilder, resumeAfterApproval } from 'lousho/executor'; -import { createMockProvider, type ToolDescriptor } from 'lousho'; +import { AgentExecutor, ToolRegistry, AgentBuilder, resumeAfterApproval } from '@linuxdevil/lousho/executor'; +import { createMockProvider, type ToolDescriptor } from '@linuxdevil/lousho'; import { FileApprovalStore } from '../approvalStore'; import { FileCheckpointStore } from '../checkpointStore'; diff --git a/apps/agent-forge/server/__tests__/authorEnable.test.ts b/apps/agent-forge/server/__tests__/authorEnable.test.ts index 45f97f4a6..fdeb9a83c 100644 --- a/apps/agent-forge/server/__tests__/authorEnable.test.ts +++ b/apps/agent-forge/server/__tests__/authorEnable.test.ts @@ -3,7 +3,7 @@ import * as fs from 'node:fs'; import * as os from 'node:os'; import * as path from 'node:path'; import request from 'supertest'; -import type { AgentSpecV2 } from 'lousho'; +import type { AgentSpecV2 } from '@linuxdevil/lousho'; import { createApp } from '../app'; import { STUDIO_TOKEN_HEADER } from '../auth'; import { RunManager } from '../runRegistry'; diff --git a/apps/agent-forge/server/__tests__/authorSave.test.ts b/apps/agent-forge/server/__tests__/authorSave.test.ts index 2ae121601..cad57c721 100644 --- a/apps/agent-forge/server/__tests__/authorSave.test.ts +++ b/apps/agent-forge/server/__tests__/authorSave.test.ts @@ -3,7 +3,7 @@ import * as fs from 'node:fs'; import * as os from 'node:os'; import * as path from 'node:path'; import request from 'supertest'; -import type { AgentSpecV2 } from 'lousho'; +import type { AgentSpecV2 } from '@linuxdevil/lousho'; import { createApp } from '../app'; import { STUDIO_TOKEN_HEADER } from '../auth'; import { RunManager } from '../runRegistry'; diff --git a/apps/agent-forge/server/__tests__/authorSecret.test.ts b/apps/agent-forge/server/__tests__/authorSecret.test.ts index 178b89b1c..915307fdc 100644 --- a/apps/agent-forge/server/__tests__/authorSecret.test.ts +++ b/apps/agent-forge/server/__tests__/authorSecret.test.ts @@ -3,7 +3,7 @@ import * as fs from 'node:fs'; import * as os from 'node:os'; import * as path from 'node:path'; import request from 'supertest'; -import { mockModel } from 'lousho/testing'; +import { mockModel } from '@linuxdevil/lousho/testing'; import { createApp } from '../app'; import { STUDIO_TOKEN_HEADER } from '../auth'; import { RunManager } from '../runRegistry'; diff --git a/apps/agent-forge/server/__tests__/authorSessions.test.ts b/apps/agent-forge/server/__tests__/authorSessions.test.ts index 12bc1fa5e..6d61eb8ab 100644 --- a/apps/agent-forge/server/__tests__/authorSessions.test.ts +++ b/apps/agent-forge/server/__tests__/authorSessions.test.ts @@ -3,7 +3,7 @@ import * as fs from 'node:fs'; import * as os from 'node:os'; import * as path from 'node:path'; import request from 'supertest'; -import { mockModel } from 'lousho/testing'; +import { mockModel } from '@linuxdevil/lousho/testing'; import { createApp } from '../app'; import { STUDIO_TOKEN_HEADER } from '../auth'; import { RunManager } from '../runRegistry'; diff --git a/apps/agent-forge/server/__tests__/authorStream.test.ts b/apps/agent-forge/server/__tests__/authorStream.test.ts index 2e9b4cf36..3abc336bd 100644 --- a/apps/agent-forge/server/__tests__/authorStream.test.ts +++ b/apps/agent-forge/server/__tests__/authorStream.test.ts @@ -3,8 +3,8 @@ import * as fs from 'node:fs'; import * as os from 'node:os'; import * as path from 'node:path'; import { WebSocket } from 'ws'; -import { mockModel } from 'lousho/testing'; -import type { LLMProvider, StreamChunk } from 'lousho'; +import { mockModel } from '@linuxdevil/lousho/testing'; +import type { LLMProvider, StreamChunk } from '@linuxdevil/lousho'; import { startStudioServer, type StudioServerHandle } from '../index'; import { STUDIO_TOKEN_HEADER } from '../auth'; import { AuthorSessionStore } from '../author/sessionStore'; diff --git a/apps/agent-forge/server/__tests__/autoApproval.test.ts b/apps/agent-forge/server/__tests__/autoApproval.test.ts index 9459a2da0..e4bdfcd5c 100644 --- a/apps/agent-forge/server/__tests__/autoApproval.test.ts +++ b/apps/agent-forge/server/__tests__/autoApproval.test.ts @@ -2,7 +2,7 @@ import { mkdtempSync } from 'node:fs'; import { tmpdir } from 'node:os'; import * as path from 'node:path'; import { afterEach, describe, expect, it, vi } from 'vitest'; -import type { AutoApprovalDecision, AutoApprovalRequest } from 'lousho'; +import type { AutoApprovalDecision, AutoApprovalRequest } from '@linuxdevil/lousho'; import { autoApprovalLogEntry, studioAutoApprover } from '../autoApproval'; import { SpendLog } from '../passes/spendLog'; import { SecretsStore } from '../secretsStore'; diff --git a/apps/agent-forge/server/__tests__/bridgeAgentHost.test.ts b/apps/agent-forge/server/__tests__/bridgeAgentHost.test.ts index b6e8ea59f..2a31e6fba 100644 --- a/apps/agent-forge/server/__tests__/bridgeAgentHost.test.ts +++ b/apps/agent-forge/server/__tests__/bridgeAgentHost.test.ts @@ -12,7 +12,7 @@ import * as os from 'node:os'; import * as path from 'node:path'; import { fileURLToPath, pathToFileURL } from 'node:url'; import { afterEach, beforeEach, describe, expect, it, vi, type Mock } from 'vitest'; -import type { AgentSpecV2, BridgePush, Message } from 'lousho'; +import type { AgentSpecV2, BridgePush, Message } from '@linuxdevil/lousho'; import { BridgeAgentHost, HostReadOnlyError, HostStartError } from '../host/bridgeAgentHost'; import { BridgeClient, BridgeError } from '../host/bridgeClient'; import { HostRunNotActiveError } from '../host/agentHost'; @@ -285,7 +285,8 @@ async function realBridge(turns: unknown): Promise { const root = fs.mkdtempSync(path.join(os.tmpdir(), 'lou-w11b-real-')); const nm = path.join(root, 'node_modules'); fs.mkdirSync(nm, { recursive: true }); - fs.symlinkSync(REPO_ROOT, path.join(nm, 'lousho'), 'junction'); + fs.mkdirSync(path.join(nm, '@linuxdevil'), { recursive: true }); + fs.symlinkSync(REPO_ROOT, path.join(nm, '@linuxdevil', 'lousho'), 'junction'); const sdk = findWorkspaceSdk(root); if (sdk === undefined) throw new Error('junction workspace did not resolve the SDK'); const client = new BridgeClient( diff --git a/apps/agent-forge/server/__tests__/bridgeClient.test.ts b/apps/agent-forge/server/__tests__/bridgeClient.test.ts index 9e91fec27..020dad233 100644 --- a/apps/agent-forge/server/__tests__/bridgeClient.test.ts +++ b/apps/agent-forge/server/__tests__/bridgeClient.test.ts @@ -3,7 +3,7 @@ import { tmpdir } from 'node:os'; import { join } from 'node:path'; import { fileURLToPath } from 'node:url'; import { afterEach, describe, expect, it } from 'vitest'; -import type { BridgePush } from 'lousho'; +import type { BridgePush } from '@linuxdevil/lousho'; import { BridgeClient, type BridgeState } from '../host/bridgeClient'; const FAKE_BRIDGE = fileURLToPath(new URL('../host/__tests__/fakeBridge.mjs', import.meta.url)); diff --git a/apps/agent-forge/server/__tests__/bridgeGuards.test.ts b/apps/agent-forge/server/__tests__/bridgeGuards.test.ts index d17cc9bfd..928615075 100644 --- a/apps/agent-forge/server/__tests__/bridgeGuards.test.ts +++ b/apps/agent-forge/server/__tests__/bridgeGuards.test.ts @@ -12,7 +12,7 @@ import * as os from 'node:os'; import * as path from 'node:path'; import type { Express } from 'express'; import request from 'supertest'; -import { upgradeSpec, type AgentSpec } from 'lousho'; +import { upgradeSpec, type AgentSpec } from '@linuxdevil/lousho'; import { createApp } from '../app'; import { createWorkspaceContext, diff --git a/apps/agent-forge/server/__tests__/buildAgentV2.test.ts b/apps/agent-forge/server/__tests__/buildAgentV2.test.ts index 01379255d..3679af014 100644 --- a/apps/agent-forge/server/__tests__/buildAgentV2.test.ts +++ b/apps/agent-forge/server/__tests__/buildAgentV2.test.ts @@ -14,7 +14,7 @@ import { createAgentConfigOf, type AgentEvent, type AgentSpecV2, -} from 'lousho'; +} from '@linuxdevil/lousho'; type Global = { __hookOrder?: string[] }; import { buildRunnableAgent, scopedApprovalStore, type RunWiring } from '../buildAgentV2'; import { FileApprovalStore } from '../approvalStore'; diff --git a/apps/agent-forge/server/__tests__/builderReporting.test.ts b/apps/agent-forge/server/__tests__/builderReporting.test.ts index ae95784d6..4e2a715d1 100644 --- a/apps/agent-forge/server/__tests__/builderReporting.test.ts +++ b/apps/agent-forge/server/__tests__/builderReporting.test.ts @@ -8,7 +8,7 @@ import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest'; import * as fs from 'node:fs'; import * as path from 'node:path'; import * as os from 'node:os'; -import type { AgentSpecV2 } from 'lousho'; +import type { AgentSpecV2 } from '@linuxdevil/lousho'; import { RunManager } from '../runRegistry'; import { newTestHost } from './testHost'; import { agentTraceDir } from '../traceStore'; diff --git a/apps/agent-forge/server/__tests__/catalogRoutes.test.ts b/apps/agent-forge/server/__tests__/catalogRoutes.test.ts index 61a7ca423..6d6b12d59 100644 --- a/apps/agent-forge/server/__tests__/catalogRoutes.test.ts +++ b/apps/agent-forge/server/__tests__/catalogRoutes.test.ts @@ -4,7 +4,7 @@ import * as os from 'node:os'; import * as path from 'node:path'; import express, { type Express } from 'express'; import request from 'supertest'; -import { listBuiltinTools, listGuardrails, listModels, listTriggerTypes } from 'lousho'; +import { listBuiltinTools, listGuardrails, listModels, listTriggerTypes } from '@linuxdevil/lousho'; import { registerCatalogRoutes } from '../routes/catalogRoutes'; import { SecretsStore } from '../secretsStore'; import { sdkRootDir } from '../sdkRoot'; diff --git a/apps/agent-forge/server/__tests__/chainTriggers.test.ts b/apps/agent-forge/server/__tests__/chainTriggers.test.ts index 0256531ce..2c9bc3a9d 100644 --- a/apps/agent-forge/server/__tests__/chainTriggers.test.ts +++ b/apps/agent-forge/server/__tests__/chainTriggers.test.ts @@ -2,8 +2,8 @@ import { EventEmitter } from 'node:events'; import { describe, expect, it, vi } from 'vitest'; -import type { AgentSpecV2 } from 'lousho'; -import type { TriggerSpec } from 'lousho/spec'; +import type { AgentSpecV2 } from '@linuxdevil/lousho'; +import type { TriggerSpec } from '@linuxdevil/lousho/spec'; import type { AgentRepo } from '../storage/agentRepo'; import type { RunManager } from '../runRegistry'; diff --git a/apps/agent-forge/server/__tests__/channelTriggers.test.ts b/apps/agent-forge/server/__tests__/channelTriggers.test.ts index 1c717c82f..a07139f5c 100644 --- a/apps/agent-forge/server/__tests__/channelTriggers.test.ts +++ b/apps/agent-forge/server/__tests__/channelTriggers.test.ts @@ -11,9 +11,9 @@ import * as path from 'node:path'; import { afterEach, describe, expect, it, vi } from 'vitest'; import express, { type Express } from 'express'; import request from 'supertest'; -import { createAgent, type AgentSpecV2, type SimpleAgent } from 'lousho'; -import { mockModel } from 'lousho/testing'; -import type { SecretRef, TriggerSpec } from 'lousho/spec'; +import { createAgent, type AgentSpecV2, type SimpleAgent } from '@linuxdevil/lousho'; +import { mockModel } from '@linuxdevil/lousho/testing'; +import type { SecretRef, TriggerSpec } from '@linuxdevil/lousho/spec'; import { AgentRepo } from '../storage/agentRepo'; import { DraftStore } from '../storage/draftStore'; @@ -31,7 +31,7 @@ import type { CronTriggers } from '../triggers/cronTriggers'; import type { WorkspaceContext } from '../workspace/context'; import type { WorkspaceHostSelector } from '../host/hostSelector'; import { workspaceRefFor } from '../workspace/workspaceId'; -import { TriggerRegistry } from 'lousho/triggers'; +import { TriggerRegistry } from '@linuxdevil/lousho/triggers'; import { CHANNEL_SECRET_KEYS, ChannelTriggers, diff --git a/apps/agent-forge/server/__tests__/chat.test.ts b/apps/agent-forge/server/__tests__/chat.test.ts index 84fa86c82..6bc3a023f 100644 --- a/apps/agent-forge/server/__tests__/chat.test.ts +++ b/apps/agent-forge/server/__tests__/chat.test.ts @@ -3,7 +3,7 @@ import * as fs from 'node:fs'; import * as path from 'node:path'; import * as os from 'node:os'; import request from 'supertest'; -import type { AgentSpecV2 } from 'lousho'; +import type { AgentSpecV2 } from '@linuxdevil/lousho'; import { createApp } from '../app'; import { RunManager, AlreadyRunningError, ApprovalPendingError } from '../runRegistry'; import { newTestHost } from './testHost'; diff --git a/apps/agent-forge/server/__tests__/chatReconcile.test.ts b/apps/agent-forge/server/__tests__/chatReconcile.test.ts index 0658e0b18..301193905 100644 --- a/apps/agent-forge/server/__tests__/chatReconcile.test.ts +++ b/apps/agent-forge/server/__tests__/chatReconcile.test.ts @@ -1,5 +1,5 @@ import { describe, it, expect } from 'vitest'; -import type { Message } from 'lousho'; +import type { Message } from '@linuxdevil/lousho'; import { reconcileChatMessages, markSteerInputs } from '../chatReconcile'; import type { ChatMessage, SteerInfo } from '../../shared/wireTypes'; diff --git a/apps/agent-forge/server/__tests__/checkpointStore.test.ts b/apps/agent-forge/server/__tests__/checkpointStore.test.ts index 38070e038..7eb5cf4e3 100644 --- a/apps/agent-forge/server/__tests__/checkpointStore.test.ts +++ b/apps/agent-forge/server/__tests__/checkpointStore.test.ts @@ -7,7 +7,7 @@ import * as fs from 'node:fs'; import * as path from 'node:path'; import * as os from 'node:os'; import { FileCheckpointStore } from '../checkpointStore'; -import type { Checkpoint } from 'lousho'; +import type { Checkpoint } from '@linuxdevil/lousho'; // eslint-disable-next-line lousho/no-sdk-source -- the SDK's shared CheckpointStore contract suite; this test runs only in the SDK repo import { describeCheckpointHistoryContract } from '../../../../src/execution/__fixtures__/checkpointHistoryContract'; diff --git a/apps/agent-forge/server/__tests__/cli.test.ts b/apps/agent-forge/server/__tests__/cli.test.ts new file mode 100644 index 000000000..ef7b2af29 --- /dev/null +++ b/apps/agent-forge/server/__tests__/cli.test.ts @@ -0,0 +1,125 @@ +/** + * `lousho-studio` argument parsing and a real boot of `server/cli.ts`. + */ +import { afterEach, describe, expect, it } from 'vitest'; +import { spawn, type ChildProcess } from 'node:child_process'; +import * as fs from 'node:fs'; +import * as os from 'node:os'; +import * as path from 'node:path'; +import { fileURLToPath } from 'node:url'; +import { parseStudioCliArgs, StudioCliUsageError, studioVersion, USAGE } from '../cli'; + +const APP_DIR = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..', '..'); + +describe('parseStudioCliArgs', () => { + it('parses the documented flags', () => { + expect(parseStudioCliArgs(['--workspace', '/tmp/x', '--port', '5000', '--host', '0.0.0.0'])).toEqual({ + workspace: '/tmp/x', + port: 5000, + host: '0.0.0.0', + }); + expect(parseStudioCliArgs(['--port=0'])).toEqual({ port: 0 }); + expect(parseStudioCliArgs(['-v'])).toEqual({ version: true }); + expect(parseStudioCliArgs(['-h'])).toEqual({ help: true }); + expect(parseStudioCliArgs([])).toEqual({}); + }); + + it('rejects unknown flags, missing values and bad ports', () => { + for (const argv of [['--bogus'], ['--port'], ['--workspace'], ['positional']]) { + expect(() => parseStudioCliArgs(argv)).toThrow(StudioCliUsageError); + } + for (const port of ['x', '-1', '1.5', '70000']) { + expect(() => parseStudioCliArgs(['--port', port])).toThrow(StudioCliUsageError); + } + expect(() => parseStudioCliArgs(['--port', 'x'])).toThrow(/--port must be an integer between 0 and 65535/); + }); +}); + +describe('studioVersion', () => { + it('reads the package version relative to the module dir', () => { + const version = studioVersion(path.join(APP_DIR, 'server')); + expect(version).toMatch(/^\d+\.\d+\.\d+/); + const pkg = JSON.parse(fs.readFileSync(path.join(APP_DIR, 'package.json'), 'utf8')) as { version: string }; + expect(version).toBe(pkg.version); + }); +}); + +describe('lousho-studio boot', () => { + let child: ChildProcess | undefined; + let tmp: string | undefined; + + afterEach(async () => { + if (child && child.exitCode === null) { + child.kill('SIGTERM'); + await new Promise((resolve) => child!.once('exit', resolve)); + } + child = undefined; + if (tmp) fs.rmSync(tmp, { recursive: true, force: true }); + tmp = undefined; + }); + + it('--help exits 0 with usage; bad args exit 2', async () => { + const run = (args: string[]) => + new Promise<{ code: number | null; stdout: string; stderr: string }>((resolve) => { + const p = spawn(process.execPath, ['--import', 'tsx', path.join(APP_DIR, 'server', 'cli.ts'), ...args], { + cwd: APP_DIR, + env: { ...process.env, LOUSHO_STUDIO_COLLECTOR: '0', LOUSHO_STUDIO_JUDGE: '0' }, + }); + let stdout = ''; + let stderr = ''; + p.stdout!.on('data', (d) => (stdout += d)); + p.stderr!.on('data', (d) => (stderr += d)); + p.on('exit', (code) => resolve({ code, stdout, stderr })); + }); + const help = await run(['--help']); + expect(help.code).toBe(0); + expect(help.stdout).toContain('Usage: lousho-studio'); + expect(USAGE).toContain('--workspace '); + const bad = await run(['--port', 'x']); + expect(bad.code).toBe(2); + expect(bad.stderr).toContain('lousho-studio:'); + tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'lousho-cli-')); + const noFolder = await run(['--workspace', path.join(tmp, 'nope')]); + expect(noFolder.code).toBe(2); + expect(noFolder.stderr).toContain("is not a folder"); + }, 60_000); + + it('boots on --port 0, prints the tokened URL, /health answers, SIGTERM exits 0', async () => { + tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'lousho-cli-')); + child = spawn( + process.execPath, + ['--import', 'tsx', path.join(APP_DIR, 'server', 'cli.ts'), '--port', '0', '--workspace', tmp], + { + cwd: APP_DIR, + env: { ...process.env, LOUSHO_STUDIO_COLLECTOR: '0', LOUSHO_STUDIO_JUDGE: '0' }, + stdio: ['ignore', 'pipe', 'pipe'], + } + ); + const url = await new Promise((resolve, reject) => { + let out = ''; + const timer = setTimeout(() => reject(new Error(`timed out waiting for URL; output so far:\n${out}`)), 45_000); + child!.stdout!.on('data', (d) => { + out += d; + const match = /Lousho Studio \S+: (\S+)/.exec(out); + if (match) { + clearTimeout(timer); + resolve(match[1]); + } + }); + child!.stderr!.on('data', (d) => (out += d)); + child!.on('exit', (code) => { + clearTimeout(timer); + reject(new Error(`cli exited early (${code}); output:\n${out}`)); + }); + }); + expect(url).toContain('/?token='); + const health = await fetch(new URL('/health', url)).then((r) => r.text()); + expect(health).toBe('ok'); + child.kill('SIGTERM'); + const code = await new Promise((resolve) => child!.once('exit', resolve)); + // Windows has no POSIX signals: kill() is TerminateProcess there, so the + // graceful-shutdown exit code is only observable on POSIX. + if (process.platform !== 'win32') expect(code).toBe(0); + child = undefined; + }, 90_000); +}); diff --git a/apps/agent-forge/server/__tests__/collector.test.ts b/apps/agent-forge/server/__tests__/collector.test.ts index 060b6e11d..1fe411e12 100644 --- a/apps/agent-forge/server/__tests__/collector.test.ts +++ b/apps/agent-forge/server/__tests__/collector.test.ts @@ -11,7 +11,7 @@ import * as http from 'node:http'; import * as path from 'node:path'; import * as os from 'node:os'; import type { AddressInfo } from 'node:net'; -import { startObserveDaemon, type ObserveDaemon } from 'lousho/observe'; +import { startObserveDaemon, type ObserveDaemon } from '@linuxdevil/lousho/observe'; import { COLLECTOR_TOKEN_SECRET, CollectorLockedError, diff --git a/apps/agent-forge/server/__tests__/copyAgent.test.ts b/apps/agent-forge/server/__tests__/copyAgent.test.ts index 3bbb10a31..7a245e1ac 100644 --- a/apps/agent-forge/server/__tests__/copyAgent.test.ts +++ b/apps/agent-forge/server/__tests__/copyAgent.test.ts @@ -9,7 +9,7 @@ import * as fs from 'node:fs'; import * as os from 'node:os'; import * as path from 'node:path'; import request from 'supertest'; -import type { AgentSpecV2 } from 'lousho'; +import type { AgentSpecV2 } from '@linuxdevil/lousho'; import { createApp } from '../app'; import { RunManager } from '../runRegistry'; import { newTestHost } from './testHost'; diff --git a/apps/agent-forge/server/__tests__/cronTriggers.test.ts b/apps/agent-forge/server/__tests__/cronTriggers.test.ts index 607ee88ff..dfeeb76cb 100644 --- a/apps/agent-forge/server/__tests__/cronTriggers.test.ts +++ b/apps/agent-forge/server/__tests__/cronTriggers.test.ts @@ -4,7 +4,7 @@ import * as path from 'node:path'; import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; -import type { TriggerSpec } from 'lousho/spec'; +import type { TriggerSpec } from '@linuxdevil/lousho/spec'; import type { DispatchRequest, DispatchResult, TriggerHost } from '../triggers/triggerHost'; import { CronTriggers, type CronState } from '../triggers/cronTriggers'; diff --git a/apps/agent-forge/server/__tests__/deployRunner.test.ts b/apps/agent-forge/server/__tests__/deployRunner.test.ts index 792804ff6..06869914d 100644 --- a/apps/agent-forge/server/__tests__/deployRunner.test.ts +++ b/apps/agent-forge/server/__tests__/deployRunner.test.ts @@ -11,7 +11,7 @@ import * as path from 'node:path'; import * as os from 'node:os'; import type { ChildProcess } from 'node:child_process'; import { parse as parseYaml } from 'yaml'; -import type { AgentSpec } from 'lousho'; +import type { AgentSpec } from '@linuxdevil/lousho'; vi.mock('node:child_process', async (importOriginal) => { const actual = await importOriginal(); diff --git a/apps/agent-forge/server/__tests__/draftRoutes.test.ts b/apps/agent-forge/server/__tests__/draftRoutes.test.ts index 3d8d4a2cf..fb42cdfef 100644 --- a/apps/agent-forge/server/__tests__/draftRoutes.test.ts +++ b/apps/agent-forge/server/__tests__/draftRoutes.test.ts @@ -3,7 +3,7 @@ import * as fs from 'node:fs'; import * as os from 'node:os'; import * as path from 'node:path'; import request from 'supertest'; -import type { AgentSpec } from 'lousho'; +import type { AgentSpec } from '@linuxdevil/lousho'; import { createApp } from '../app'; import { RunManager } from '../runRegistry'; import { newTestHost } from './testHost'; diff --git a/apps/agent-forge/server/__tests__/draftView.test.ts b/apps/agent-forge/server/__tests__/draftView.test.ts index b1b6926ca..b9829b33d 100644 --- a/apps/agent-forge/server/__tests__/draftView.test.ts +++ b/apps/agent-forge/server/__tests__/draftView.test.ts @@ -3,7 +3,7 @@ import * as fs from 'node:fs'; import * as os from 'node:os'; import * as path from 'node:path'; import request from 'supertest'; -import type { AgentSpec } from 'lousho'; +import type { AgentSpec } from '@linuxdevil/lousho'; import { createApp } from '../app'; import { RunManager } from '../runRegistry'; import { newTestHost } from './testHost'; diff --git a/apps/agent-forge/server/__tests__/eventsAll.test.ts b/apps/agent-forge/server/__tests__/eventsAll.test.ts index 43af7059c..4e0958e5f 100644 --- a/apps/agent-forge/server/__tests__/eventsAll.test.ts +++ b/apps/agent-forge/server/__tests__/eventsAll.test.ts @@ -5,7 +5,7 @@ import * as os from 'node:os'; import * as path from 'node:path'; import request from 'supertest'; import { WebSocket } from 'ws'; -import type { AgentSpecV2 } from 'lousho'; +import type { AgentSpecV2 } from '@linuxdevil/lousho'; import { startStudioServer, type StudioServerHandle } from '../index'; import { STUDIO_TOKEN_HEADER } from '../auth'; import { createUpgradeRouter } from '../ws/upgradeRouter'; diff --git a/apps/agent-forge/server/__tests__/eventsWs.test.ts b/apps/agent-forge/server/__tests__/eventsWs.test.ts index 08b051c9b..28cfc93eb 100644 --- a/apps/agent-forge/server/__tests__/eventsWs.test.ts +++ b/apps/agent-forge/server/__tests__/eventsWs.test.ts @@ -4,7 +4,7 @@ import * as os from 'node:os'; import * as path from 'node:path'; import request from 'supertest'; import { WebSocket } from 'ws'; -import type { AgentSpecV2 } from 'lousho'; +import type { AgentSpecV2 } from '@linuxdevil/lousho'; import { startStudioServer, type StudioServerHandle } from '../index'; import { STUDIO_TOKEN_HEADER } from '../auth'; import { createApp } from '../app'; diff --git a/apps/agent-forge/server/__tests__/export.test.ts b/apps/agent-forge/server/__tests__/export.test.ts index 44df01ce6..435a8c5ac 100644 --- a/apps/agent-forge/server/__tests__/export.test.ts +++ b/apps/agent-forge/server/__tests__/export.test.ts @@ -4,7 +4,7 @@ import * as os from 'node:os'; import * as path from 'node:path'; import request from 'supertest'; import { parse as parseYaml, stringify as stringifyYaml } from 'yaml'; -import type { AgentSpecV2 } from 'lousho'; +import type { AgentSpecV2 } from '@linuxdevil/lousho'; import { createApp } from '../app'; import { RunManager } from '../runRegistry'; import { newTestHost } from './testHost'; diff --git a/apps/agent-forge/server/__tests__/fixtures/scriptedProvider.mjs b/apps/agent-forge/server/__tests__/fixtures/scriptedProvider.mjs index ac288462d..2d2100243 100644 --- a/apps/agent-forge/server/__tests__/fixtures/scriptedProvider.mjs +++ b/apps/agent-forge/server/__tests__/fixtures/scriptedProvider.mjs @@ -1,19 +1,18 @@ -// W11b test preload: registers a scripted `scripted` provider inside the -// bridge child process (passed as `node --import ` before -// `bin/lousho.js bridge`), where providers registered in the Studio test -// process cannot reach. The turn script comes from SCRIPTED_TURNS (a JSON -// array of mockModel turns - see src/testing/mockModel.ts). -// -// The mockModel is created ONCE here, not per factory call: the bridge -// rebuilds the runnable agent when an approval pause continues, and a -// fresh script per build would replay turn 1 (pausing again) instead of -// advancing to the continuation turn - the same reason the SDK's own -// bridge tests register one shared `model`. -// -// Imports `lousho`, not `@lousho/build-ai-agent`: that is the name the -// package ships under today; the scoped rename lands with W13a/W13b. -import { LLMProviderRegistry } from 'lousho'; -import { mockModel } from 'lousho/testing'; - -const model = mockModel(JSON.parse(process.env.SCRIPTED_TURNS ?? '["ok"]'), { onExhausted: 'repeat-last' }); -LLMProviderRegistry.register('scripted', () => model); +// W11b test preload: registers a scripted `scripted` provider inside the +// bridge child process (passed as `node --import ` before +// `bin/lousho.js bridge`), where providers registered in the Studio test +// process cannot reach. The turn script comes from SCRIPTED_TURNS (a JSON +// array of mockModel turns - see src/testing/mockModel.ts). +// +// The mockModel is created ONCE here, not per factory call: the bridge +// rebuilds the runnable agent when an approval pause continues, and a +// fresh script per build would replay turn 1 (pausing again) instead of +// advancing to the continuation turn - the same reason the SDK's own +// bridge tests register one shared `model`. +// +// Imports `@linuxdevil/lousho`, the name the package ships under. +import { LLMProviderRegistry } from '@linuxdevil/lousho'; +import { mockModel } from '@linuxdevil/lousho/testing'; + +const model = mockModel(JSON.parse(process.env.SCRIPTED_TURNS ?? '["ok"]'), { onExhausted: 'repeat-last' }); +LLMProviderRegistry.register('scripted', () => model); diff --git a/apps/agent-forge/server/__tests__/hooksRoute.test.ts b/apps/agent-forge/server/__tests__/hooksRoute.test.ts index bea888b0e..3936ff7e5 100644 --- a/apps/agent-forge/server/__tests__/hooksRoute.test.ts +++ b/apps/agent-forge/server/__tests__/hooksRoute.test.ts @@ -3,8 +3,8 @@ import { createHmac } from 'node:crypto'; import { describe, expect, it, vi, type Mock } from 'vitest'; import type { Express } from 'express'; import request from 'supertest'; -import { TriggerRegistry } from 'lousho/triggers'; -import type { TriggerSpec } from 'lousho/spec'; +import { TriggerRegistry } from '@linuxdevil/lousho/triggers'; +import type { TriggerSpec } from '@linuxdevil/lousho/spec'; import { createApp } from '../app'; import { SecretsStore } from '../secretsStore'; diff --git a/apps/agent-forge/server/__tests__/hostRoutes.test.ts b/apps/agent-forge/server/__tests__/hostRoutes.test.ts index 00af6a3ab..f1cf3aee3 100644 --- a/apps/agent-forge/server/__tests__/hostRoutes.test.ts +++ b/apps/agent-forge/server/__tests__/hostRoutes.test.ts @@ -1,265 +1,265 @@ -/** - * W11c: `GET /workspaces/:ws/host` + `POST /workspaces/:ws/host/restart` - * over real `createWorkspaceContext`s whose `WorkspaceHostSelector`s are - * driven by W11a's `fakeBridge.mjs` installed as each workspace's SDK. - * The `selectHost` services seam injects short client timeouts and - * per-workspace `FAKE_BRIDGE_*` env; `process.env` propagation covers the - * rest (bridgeEnv spreads it). - */ -import { describe, it, expect, beforeEach, afterEach } from 'vitest'; -import * as fs from 'node:fs'; -import * as os from 'node:os'; -import * as path from 'node:path'; -import { fileURLToPath } from 'node:url'; -import type { Express } from 'express'; -import request from 'supertest'; -import type { StudioEvent } from '../../shared/wireTypes'; -import type { WorkspaceRef } from '../../shared/workspace'; -import { createApp } from '../app'; -import { createWorkspaceContext, type WorkspaceContext, type WorkspaceResolver, type WorkspaceServices } from '../workspace/context'; -import { WorkspaceHostSelector } from '../host/hostSelector'; -import { WorkspaceRegistry } from '../workspace/workspaceRegistry'; -import { SecretsStore } from '../secretsStore'; -import { SettingsStore } from '../settingsStore'; -import { SpendLog } from '../passes/spendLog'; -import { AlertStore } from '../inbox/alertStore'; -import { PassRunner } from '../passes/passRunner'; - -const FAKE_BRIDGE = fileURLToPath(new URL('../host/__tests__/fakeBridge.mjs', import.meta.url)); - -/** A workspace whose node_modules SDK's `bin/lousho.js` IS the fake bridge. */ -function sdkWorkspace(root: string): void { - const pkgDir = path.join(root, 'node_modules', '@lousho', 'build-ai-agent'); - fs.mkdirSync(path.join(pkgDir, 'bin'), { recursive: true }); - fs.mkdirSync(path.join(pkgDir, 'dist', 'cli'), { recursive: true }); - fs.writeFileSync(path.join(pkgDir, 'package.json'), JSON.stringify({ name: '@lousho/build-ai-agent', version: '9.9.9-test' })); - fs.copyFileSync(FAKE_BRIDGE, path.join(pkgDir, 'bin', 'lousho.js')); - fs.writeFileSync(path.join(pkgDir, 'dist', 'cli', 'bridge.mjs'), '// built\n'); -} - -describe('host routes (W11c)', () => { - let dir: string; - let studioDir: string; - let registry: WorkspaceRegistry; - let services: WorkspaceServices; - /** root -> extra env for that workspace's bridge child. */ - let envByRoot: Map>; - let contexts: WorkspaceContext[]; - let app: Express; - let launch: WorkspaceContext; - let inProcessCtx: WorkspaceContext; - let bridgeCtx: WorkspaceContext; - let readOnlyCtx: WorkspaceContext; - let failedCtx: WorkspaceContext; - let gateFile: string; - - async function ctxFor(root: string): Promise { - const ref: WorkspaceRef = await registry.ensure(root); - const ctx = createWorkspaceContext(ref, services); - contexts.push(ctx); - return ctx; - } - - beforeEach(async () => { - dir = fs.mkdtempSync(path.join(os.tmpdir(), 'lou-w11c-host-')); - studioDir = path.join(dir, 'studio'); - fs.mkdirSync(studioDir, { recursive: true }); - const secretsStore = new SecretsStore(studioDir); - const settingsStore = new SettingsStore(studioDir); - const spendLog = new SpendLog(path.join(studioDir, 'spend.jsonl')); - const alerts = new AlertStore(path.join(studioDir, 'alerts.json')); - registry = new WorkspaceRegistry(path.join(studioDir, 'workspaces.json')); - envByRoot = new Map(); - contexts = []; - services = { - secretsStore, - settingsStore, - observe: false, - passRunner: new PassRunner({ baseDir: studioDir, spendLog, isEnabled: () => false }), - spendLog, - alerts, - collector: { url: () => 'http://127.0.0.1:9', token: () => undefined }, - studioVersion: '9.9.9-studio', - selectHost: (ref, deps) => - new WorkspaceHostSelector(ref, { - ...deps, - clientOptions: { stderr: () => undefined, timeouts: { hello: 400, shutdown: 200 } }, - extraEnv: envByRoot.get(ref.root), - }), - }; - - const roots = { - inProcess: path.join(dir, 'plain'), - bridge: path.join(dir, 'sdk-ok'), - readOnly: path.join(dir, 'sdk-old'), - failed: path.join(dir, 'sdk-gate'), - }; - for (const root of Object.values(roots)) fs.mkdirSync(root, { recursive: true }); - sdkWorkspace(roots.bridge); - sdkWorkspace(roots.readOnly); - sdkWorkspace(roots.failed); - envByRoot.set(roots.readOnly, { FAKE_BRIDGE_SPEC_VERSIONS: '[1]' }); - gateFile = path.join(dir, 'gate'); - envByRoot.set(roots.failed, { FAKE_BRIDGE_SILENT_UNTIL: gateFile }); - - inProcessCtx = await ctxFor(roots.inProcess); - bridgeCtx = await ctxFor(roots.bridge); - readOnlyCtx = await ctxFor(roots.readOnly); - failedCtx = await ctxFor(roots.failed); - launch = inProcessCtx; - - const byId = new Map(contexts.map((c) => [c.ref.id, c])); - const resolver: WorkspaceResolver = { - resolve: async (id) => byId.get(id ?? launch.ref.id), - defaultId: () => launch.ref.id, - retain: () => () => undefined, - }; - app = createApp({ - workspace: launch, - workspaces: resolver, - baseDir: roots.inProcess, - studioHome: studioDir, - secretsStore, - settingsStore, - spendLog, - passRunner: services.passRunner, - alerts, - }); - }); - - afterEach(async () => { - await Promise.all(contexts.map((c) => c.dispose().catch(() => undefined))); - fs.rmSync(dir, { recursive: true, force: true }); - }); - - const get = (ws: string) => request(app).get(`/workspaces/${ws}/host`); - const post = (ws: string) => request(app).post(`/workspaces/${ws}/host/restart`); - - it('GET on an in-process workspace reports the info banner and ready state', async () => { - const res = await get(inProcessCtx.ref.id); - expect(res.status).toBe(200); - expect(res.body).toMatchObject({ - workspaceId: inProcessCtx.ref.id, - mode: 'in-process', - state: 'ready', - sdkVersion: '9.9.9-studio', - studioSdkVersion: expect.any(String), - }); - expect(res.body.banner).toMatchObject({ tone: 'info' }); - expect(res.body.banner.text).toContain('no @lousho/build-ai-agent'); - expect(res.body.recentStderr).toBeUndefined(); - }); - - it('GET on a bridge workspace reports bridge/ready with the hello sdkVersion', async () => { - const res = await get(bridgeCtx.ref.id); - expect(res.status).toBe(200); - expect(res.body).toMatchObject({ - workspaceId: bridgeCtx.ref.id, - mode: 'bridge', - state: 'ready', - sdkVersion: '0.0.0-fake', - studioSdkVersion: expect.any(String), - banner: null, - }); - }); - - it('GET on a read-only workspace reports the update warning', async () => { - const res = await get(readOnlyCtx.ref.id); - expect(res.status).toBe(200); - expect(res.body).toMatchObject({ workspaceId: readOnlyCtx.ref.id, mode: 'read-only', state: 'ready' }); - expect(res.body.banner).toMatchObject({ tone: 'warning' }); - expect(res.body.banner.text).toContain('@lousho/build-ai-agent'); - }); - - it('GET on a crashed bridge reports failed with the restart banner and stderr', async () => { - const res = await get(failedCtx.ref.id); - expect(res.status).toBe(200); - expect(res.body).toMatchObject({ - workspaceId: failedCtx.ref.id, - mode: 'bridge', - state: 'failed', - banner: { tone: 'danger', action: 'restart' }, - }); - expect(res.body.recentStderr).toEqual( - expect.arrayContaining([expect.stringContaining('silent until the gate file')]) - ); - }); - - it('GET answers 404 for an unknown workspace and 400 for a malformed id', async () => { - const missing = await get('unknown-000001'); - expect(missing.status).toBe(404); - expect(missing.body.code).toBe('LOUSHO_WORKSPACE_NOT_FOUND'); - const bad = await get('not a workspace!'); - expect(bad.status).toBe(400); - expect(bad.body.code).toBe('LOUSHO_WORKSPACE_ID_INVALID'); - }); - - it('POST restart on an in-process workspace is a 409 LOUSHO_HOST_NOT_BRIDGE', async () => { - const res = await post(inProcessCtx.ref.id); - expect(res.status).toBe(409); - expect(res.body).toEqual({ error: 'Only a project bridge can be restarted.', code: 'LOUSHO_HOST_NOT_BRIDGE' }); - }); - - it('POST restart on a read-only workspace re-runs the handshake (still read-only)', async () => { - const res = await post(readOnlyCtx.ref.id); - expect(res.status).toBe(202); - expect(res.body.mode).toBe('read-only'); - }); - - it('a read-only workspace reads agents but refuses writes and runs', async () => { - const ws = { 'x-lousho-workspace': readOnlyCtx.ref.id }; - const list = await request(app).get('/agents').set(ws); - expect(list.status).toBe(200); - const save = await request(app).put('/agents/some-agent').set(ws).send({ spec: { version: 2 } }); - expect(save.status).toBe(409); - expect(save.body.code).toBe('LOUSHO_WORKSPACE_READ_ONLY'); - const run = await request(app).post('/agents/some-agent/run').set(ws).send({}); - expect(run.status).toBe(409); - expect(run.body.code).toBe('LOUSHO_WORKSPACE_READ_ONLY'); - // Studio-global routes are never gated. - const catalog = await request(app).get('/catalog/tools').set(ws); - expect(catalog.status).toBe(200); - }); - - it('POST restart revives a crashed bridge and publishes host.status events', async () => { - const events: StudioEvent[] = []; - const off = failedCtx.events.subscribe((e) => events.push(e)); - try { - // The gate file flips the fake bridge from silent to answering hello. - fs.writeFileSync(gateFile, 'go'); - const res = await post(failedCtx.ref.id); - expect(res.status).toBe(202); - expect(res.body).toMatchObject({ mode: 'bridge', state: 'ready', banner: null, sdkVersion: '0.0.0-fake' }); - const statuses = events.filter((e) => e.type === 'host.status'); - expect(statuses.length).toBeGreaterThan(0); - expect(statuses.at(-1)).toMatchObject({ - type: 'host.status', - payload: { workspaceId: failedCtx.ref.id, mode: 'bridge', state: 'ready', banner: null }, - }); - } finally { - off(); - } - }); - - it('a settling handshake publishes host.status on the workspace bus', async () => { - // A fresh bridge context: subscribe before its handshake settles. - const lateRoot = path.join(dir, 'sdk-late'); - fs.mkdirSync(lateRoot, { recursive: true }); - sdkWorkspace(lateRoot); - const events: StudioEvent[] = []; - const ctx = await ctxFor(lateRoot); - const off = ctx.events.subscribe((e) => events.push(e)); - try { - await ctx.hostSelector.ready(); - // Selection settle AND the client's ready transition each publish once. - const statuses = events.filter((e) => e.type === 'host.status'); - expect(statuses.length).toBeGreaterThanOrEqual(1); - expect(statuses.at(-1)).toMatchObject({ - type: 'host.status', - payload: { workspaceId: ctx.ref.id, mode: 'bridge', state: 'ready', banner: null }, - }); - } finally { - off(); - } - }); -}); +/** + * W11c: `GET /workspaces/:ws/host` + `POST /workspaces/:ws/host/restart` + * over real `createWorkspaceContext`s whose `WorkspaceHostSelector`s are + * driven by W11a's `fakeBridge.mjs` installed as each workspace's SDK. + * The `selectHost` services seam injects short client timeouts and + * per-workspace `FAKE_BRIDGE_*` env; `process.env` propagation covers the + * rest (bridgeEnv spreads it). + */ +import { describe, it, expect, beforeEach, afterEach } from 'vitest'; +import * as fs from 'node:fs'; +import * as os from 'node:os'; +import * as path from 'node:path'; +import { fileURLToPath } from 'node:url'; +import type { Express } from 'express'; +import request from 'supertest'; +import type { StudioEvent } from '../../shared/wireTypes'; +import type { WorkspaceRef } from '../../shared/workspace'; +import { createApp } from '../app'; +import { createWorkspaceContext, type WorkspaceContext, type WorkspaceResolver, type WorkspaceServices } from '../workspace/context'; +import { WorkspaceHostSelector } from '../host/hostSelector'; +import { WorkspaceRegistry } from '../workspace/workspaceRegistry'; +import { SecretsStore } from '../secretsStore'; +import { SettingsStore } from '../settingsStore'; +import { SpendLog } from '../passes/spendLog'; +import { AlertStore } from '../inbox/alertStore'; +import { PassRunner } from '../passes/passRunner'; + +const FAKE_BRIDGE = fileURLToPath(new URL('../host/__tests__/fakeBridge.mjs', import.meta.url)); + +/** A workspace whose node_modules SDK's `bin/lousho.js` IS the fake bridge. */ +function sdkWorkspace(root: string): void { + const pkgDir = path.join(root, 'node_modules', '@linuxdevil', 'lousho'); + fs.mkdirSync(path.join(pkgDir, 'bin'), { recursive: true }); + fs.mkdirSync(path.join(pkgDir, 'dist', 'cli'), { recursive: true }); + fs.writeFileSync(path.join(pkgDir, 'package.json'), JSON.stringify({ name: '@linuxdevil/lousho', version: '9.9.9-test' })); + fs.copyFileSync(FAKE_BRIDGE, path.join(pkgDir, 'bin', 'lousho.js')); + fs.writeFileSync(path.join(pkgDir, 'dist', 'cli', 'bridge.mjs'), '// built\n'); +} + +describe('host routes (W11c)', () => { + let dir: string; + let studioDir: string; + let registry: WorkspaceRegistry; + let services: WorkspaceServices; + /** root -> extra env for that workspace's bridge child. */ + let envByRoot: Map>; + let contexts: WorkspaceContext[]; + let app: Express; + let launch: WorkspaceContext; + let inProcessCtx: WorkspaceContext; + let bridgeCtx: WorkspaceContext; + let readOnlyCtx: WorkspaceContext; + let failedCtx: WorkspaceContext; + let gateFile: string; + + async function ctxFor(root: string): Promise { + const ref: WorkspaceRef = await registry.ensure(root); + const ctx = createWorkspaceContext(ref, services); + contexts.push(ctx); + return ctx; + } + + beforeEach(async () => { + dir = fs.mkdtempSync(path.join(os.tmpdir(), 'lou-w11c-host-')); + studioDir = path.join(dir, 'studio'); + fs.mkdirSync(studioDir, { recursive: true }); + const secretsStore = new SecretsStore(studioDir); + const settingsStore = new SettingsStore(studioDir); + const spendLog = new SpendLog(path.join(studioDir, 'spend.jsonl')); + const alerts = new AlertStore(path.join(studioDir, 'alerts.json')); + registry = new WorkspaceRegistry(path.join(studioDir, 'workspaces.json')); + envByRoot = new Map(); + contexts = []; + services = { + secretsStore, + settingsStore, + observe: false, + passRunner: new PassRunner({ baseDir: studioDir, spendLog, isEnabled: () => false }), + spendLog, + alerts, + collector: { url: () => 'http://127.0.0.1:9', token: () => undefined }, + studioVersion: '9.9.9-studio', + selectHost: (ref, deps) => + new WorkspaceHostSelector(ref, { + ...deps, + clientOptions: { stderr: () => undefined, timeouts: { hello: 400, shutdown: 200 } }, + extraEnv: envByRoot.get(ref.root), + }), + }; + + const roots = { + inProcess: path.join(dir, 'plain'), + bridge: path.join(dir, 'sdk-ok'), + readOnly: path.join(dir, 'sdk-old'), + failed: path.join(dir, 'sdk-gate'), + }; + for (const root of Object.values(roots)) fs.mkdirSync(root, { recursive: true }); + sdkWorkspace(roots.bridge); + sdkWorkspace(roots.readOnly); + sdkWorkspace(roots.failed); + envByRoot.set(roots.readOnly, { FAKE_BRIDGE_SPEC_VERSIONS: '[1]' }); + gateFile = path.join(dir, 'gate'); + envByRoot.set(roots.failed, { FAKE_BRIDGE_SILENT_UNTIL: gateFile }); + + inProcessCtx = await ctxFor(roots.inProcess); + bridgeCtx = await ctxFor(roots.bridge); + readOnlyCtx = await ctxFor(roots.readOnly); + failedCtx = await ctxFor(roots.failed); + launch = inProcessCtx; + + const byId = new Map(contexts.map((c) => [c.ref.id, c])); + const resolver: WorkspaceResolver = { + resolve: async (id) => byId.get(id ?? launch.ref.id), + defaultId: () => launch.ref.id, + retain: () => () => undefined, + }; + app = createApp({ + workspace: launch, + workspaces: resolver, + baseDir: roots.inProcess, + studioHome: studioDir, + secretsStore, + settingsStore, + spendLog, + passRunner: services.passRunner, + alerts, + }); + }); + + afterEach(async () => { + await Promise.all(contexts.map((c) => c.dispose().catch(() => undefined))); + fs.rmSync(dir, { recursive: true, force: true }); + }); + + const get = (ws: string) => request(app).get(`/workspaces/${ws}/host`); + const post = (ws: string) => request(app).post(`/workspaces/${ws}/host/restart`); + + it('GET on an in-process workspace reports the info banner and ready state', async () => { + const res = await get(inProcessCtx.ref.id); + expect(res.status).toBe(200); + expect(res.body).toMatchObject({ + workspaceId: inProcessCtx.ref.id, + mode: 'in-process', + state: 'ready', + sdkVersion: '9.9.9-studio', + studioSdkVersion: expect.any(String), + }); + expect(res.body.banner).toMatchObject({ tone: 'info' }); + expect(res.body.banner.text).toContain('no @linuxdevil/lousho'); + expect(res.body.recentStderr).toBeUndefined(); + }); + + it('GET on a bridge workspace reports bridge/ready with the hello sdkVersion', async () => { + const res = await get(bridgeCtx.ref.id); + expect(res.status).toBe(200); + expect(res.body).toMatchObject({ + workspaceId: bridgeCtx.ref.id, + mode: 'bridge', + state: 'ready', + sdkVersion: '0.0.0-fake', + studioSdkVersion: expect.any(String), + banner: null, + }); + }); + + it('GET on a read-only workspace reports the update warning', async () => { + const res = await get(readOnlyCtx.ref.id); + expect(res.status).toBe(200); + expect(res.body).toMatchObject({ workspaceId: readOnlyCtx.ref.id, mode: 'read-only', state: 'ready' }); + expect(res.body.banner).toMatchObject({ tone: 'warning' }); + expect(res.body.banner.text).toContain('@linuxdevil/lousho'); + }); + + it('GET on a crashed bridge reports failed with the restart banner and stderr', async () => { + const res = await get(failedCtx.ref.id); + expect(res.status).toBe(200); + expect(res.body).toMatchObject({ + workspaceId: failedCtx.ref.id, + mode: 'bridge', + state: 'failed', + banner: { tone: 'danger', action: 'restart' }, + }); + expect(res.body.recentStderr).toEqual( + expect.arrayContaining([expect.stringContaining('silent until the gate file')]) + ); + }); + + it('GET answers 404 for an unknown workspace and 400 for a malformed id', async () => { + const missing = await get('unknown-000001'); + expect(missing.status).toBe(404); + expect(missing.body.code).toBe('LOUSHO_WORKSPACE_NOT_FOUND'); + const bad = await get('not a workspace!'); + expect(bad.status).toBe(400); + expect(bad.body.code).toBe('LOUSHO_WORKSPACE_ID_INVALID'); + }); + + it('POST restart on an in-process workspace is a 409 LOUSHO_HOST_NOT_BRIDGE', async () => { + const res = await post(inProcessCtx.ref.id); + expect(res.status).toBe(409); + expect(res.body).toEqual({ error: 'Only a project bridge can be restarted.', code: 'LOUSHO_HOST_NOT_BRIDGE' }); + }); + + it('POST restart on a read-only workspace re-runs the handshake (still read-only)', async () => { + const res = await post(readOnlyCtx.ref.id); + expect(res.status).toBe(202); + expect(res.body.mode).toBe('read-only'); + }); + + it('a read-only workspace reads agents but refuses writes and runs', async () => { + const ws = { 'x-lousho-workspace': readOnlyCtx.ref.id }; + const list = await request(app).get('/agents').set(ws); + expect(list.status).toBe(200); + const save = await request(app).put('/agents/some-agent').set(ws).send({ spec: { version: 2 } }); + expect(save.status).toBe(409); + expect(save.body.code).toBe('LOUSHO_WORKSPACE_READ_ONLY'); + const run = await request(app).post('/agents/some-agent/run').set(ws).send({}); + expect(run.status).toBe(409); + expect(run.body.code).toBe('LOUSHO_WORKSPACE_READ_ONLY'); + // Studio-global routes are never gated. + const catalog = await request(app).get('/catalog/tools').set(ws); + expect(catalog.status).toBe(200); + }); + + it('POST restart revives a crashed bridge and publishes host.status events', async () => { + const events: StudioEvent[] = []; + const off = failedCtx.events.subscribe((e) => events.push(e)); + try { + // The gate file flips the fake bridge from silent to answering hello. + fs.writeFileSync(gateFile, 'go'); + const res = await post(failedCtx.ref.id); + expect(res.status).toBe(202); + expect(res.body).toMatchObject({ mode: 'bridge', state: 'ready', banner: null, sdkVersion: '0.0.0-fake' }); + const statuses = events.filter((e) => e.type === 'host.status'); + expect(statuses.length).toBeGreaterThan(0); + expect(statuses.at(-1)).toMatchObject({ + type: 'host.status', + payload: { workspaceId: failedCtx.ref.id, mode: 'bridge', state: 'ready', banner: null }, + }); + } finally { + off(); + } + }); + + it('a settling handshake publishes host.status on the workspace bus', async () => { + // A fresh bridge context: subscribe before its handshake settles. + const lateRoot = path.join(dir, 'sdk-late'); + fs.mkdirSync(lateRoot, { recursive: true }); + sdkWorkspace(lateRoot); + const events: StudioEvent[] = []; + const ctx = await ctxFor(lateRoot); + const off = ctx.events.subscribe((e) => events.push(e)); + try { + await ctx.hostSelector.ready(); + // Selection settle AND the client's ready transition each publish once. + const statuses = events.filter((e) => e.type === 'host.status'); + expect(statuses.length).toBeGreaterThanOrEqual(1); + expect(statuses.at(-1)).toMatchObject({ + type: 'host.status', + payload: { workspaceId: ctx.ref.id, mode: 'bridge', state: 'ready', banner: null }, + }); + } finally { + off(); + } + }); +}); diff --git a/apps/agent-forge/server/__tests__/hostSelector.test.ts b/apps/agent-forge/server/__tests__/hostSelector.test.ts index c062eb7ef..403ec6645 100644 --- a/apps/agent-forge/server/__tests__/hostSelector.test.ts +++ b/apps/agent-forge/server/__tests__/hostSelector.test.ts @@ -1,211 +1,211 @@ -/** - * W11b: `WorkspaceHostSelector` rows 1-7. Rows 1-2 decide from disk and - * never spawn; rows 3-7 run the real `BridgeClient` handshake against - * `fakeBridge.mjs` (installed as the workspace's `node_modules` SDK). - */ -import * as fs from 'node:fs'; -import * as os from 'node:os'; -import * as path from 'node:path'; -import { fileURLToPath } from 'node:url'; -import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; -import type { WorkspaceRef } from '../../shared/workspace'; -import { BridgeAgentHost, HostReadOnlyError } from '../host/bridgeAgentHost'; -import { envCollector, WorkspaceHostSelector, type SelectHostDeps } from '../host/hostSelector'; -import { InProcessAgentHost } from '../host/inProcessAgentHost'; -import { FileCheckpointStore } from '../checkpointStore'; -import { FileApprovalStore } from '../approvalStore'; -import { MIN_BRIDGE_SDK_VERSION } from '../host/sdkCompat'; -import { workspaceRefFor } from '../workspace/workspaceId'; -import { SecretsStore } from '../secretsStore'; - -const FAKE_BRIDGE = fileURLToPath(new URL('../host/__tests__/fakeBridge.mjs', import.meta.url)); - -let dir: string; -let secretsDir: string; - -beforeEach(() => { - dir = fs.mkdtempSync(path.join(os.tmpdir(), 'lou-w11b-sel-')); - secretsDir = fs.mkdtempSync(path.join(os.tmpdir(), 'lou-w11b-sec-')); -}); - -afterEach(() => { - fs.rmSync(dir, { recursive: true, force: true }); - fs.rmSync(secretsDir, { recursive: true, force: true }); -}); - -/** A workspace whose node_modules SDK's `bin/lousho.js` IS the fake bridge. */ -function sdkWorkspace(root: string, sdkName = '@lousho/build-ai-agent'): string { - const pkgDir = path.join(root, 'node_modules', ...sdkName.split('/')); - fs.mkdirSync(path.join(pkgDir, 'bin'), { recursive: true }); - fs.mkdirSync(path.join(pkgDir, 'dist', 'cli'), { recursive: true }); - fs.writeFileSync( - path.join(pkgDir, 'package.json'), - JSON.stringify({ name: sdkName, version: '9.9.9-test' }) - ); - fs.copyFileSync(FAKE_BRIDGE, path.join(pkgDir, 'bin', 'lousho.js')); - fs.writeFileSync(path.join(pkgDir, 'dist', 'cli', 'bridge.mjs'), '// built\n'); - return pkgDir; -} - -function deps(root: string, extraEnv: Record = {}, timeouts: Record = {}): SelectHostDeps { - return { - inProcess: () => - new InProcessAgentHost({ - baseDir: root, - checkpointStore: new FileCheckpointStore(root), - approvalStore: new FileApprovalStore(root), - }), - secretsStore: new SecretsStore(secretsDir), - collector: () => ({ url: 'http://127.0.0.1:9' }), - studioVersion: '9.9.9-studio', - clientOptions: { stderr: () => undefined, timeouts: { hello: 2_000, shutdown: 500, ...timeouts } }, - extraEnv: { FAKE_BRIDGE_REPORT_ENV: '1', ...extraEnv }, - }; -} - -const refFor = (root: string): WorkspaceRef => workspaceRefFor(root); - -describe('WorkspaceHostSelector', () => { - it('row 1: no SDK on disk -> in-process, info banner, no client, reports to observe', async () => { - const root = path.join(dir, 'plain'); - fs.mkdirSync(root, { recursive: true }); - const selector = new WorkspaceHostSelector(refFor(root), deps(root)); - expect(selector.host).toBeInstanceOf(InProcessAgentHost); - expect(selector.client).toBeUndefined(); - expect(selector.reportsToObserve).toBe(true); - expect(selector.current()).toEqual({ - mode: 'in-process', - sdkVersion: '9.9.9-studio', - banner: { - tone: 'info', - text: "This project has no @lousho/build-ai-agent installed. Runs use Studio's built-in SDK 9.9.9-studio.", - }, - }); - await expect(selector.ready()).resolves.toMatchObject({ mode: 'in-process' }); - }); - - it('row 2: the SDK checkout itself, unbuilt -> in-process with the build hint', () => { - const root = path.join(dir, 'sdk-src'); - fs.mkdirSync(path.join(root, 'bin'), { recursive: true }); - fs.writeFileSync(path.join(root, 'package.json'), JSON.stringify({ name: 'lousho', version: '9.9.9' })); - fs.writeFileSync(path.join(root, 'bin', 'lousho.js'), '// bin\n'); - const selector = new WorkspaceHostSelector(refFor(root), deps(root)); - expect(selector.host).toBeInstanceOf(InProcessAgentHost); - expect(selector.client).toBeUndefined(); - expect(selector.current().banner).toEqual({ - tone: 'info', - text: "This SDK checkout is not built, so runs use Studio's built-in SDK 9.9.9-studio. Run npm run build to use it.", - }); - }); - - it('row 3: bridge child without the command -> read-only with the update warning', async () => { - const root = path.join(dir, 'ws'); - fs.mkdirSync(root, { recursive: true }); - sdkWorkspace(root); - const selector = new WorkspaceHostSelector(refFor(root), deps(root, { FAKE_BRIDGE_MODE: 'unknown-command' })); - expect(selector.host).toBeInstanceOf(BridgeAgentHost); - expect(selector.client).toBeDefined(); - expect(selector.reportsToObserve).toBe(false); - // Provisional snapshot before the handshake settles. - expect(selector.current()).toMatchObject({ mode: 'bridge', sdkVersion: '9.9.9-test', banner: null }); - const settled = await selector.ready(); - expect(settled.mode).toBe('read-only'); - expect(settled.banner).toEqual({ - tone: 'warning', - text: `Update @lousho/build-ai-agent in this project to ${MIN_BRIDGE_SDK_VERSION} or later to edit agents here.`, - }); - await expect(selector.host.start({ agentId: 'a', spec: {} as never, sessionId: 's', input: 'hi', onEvent: () => {} })).rejects.toBeInstanceOf(HostReadOnlyError); - }); - - it('row 4: hello without spec v2 -> read-only with the same update warning', async () => { - const root = path.join(dir, 'ws'); - fs.mkdirSync(root, { recursive: true }); - sdkWorkspace(root); - const selector = new WorkspaceHostSelector(refFor(root), deps(root, { FAKE_BRIDGE_SPEC_VERSIONS: '[1]' })); - const settled = await selector.ready(); - expect(settled.mode).toBe('read-only'); - expect(settled.banner?.tone).toBe('warning'); - expect(settled.banner?.text).toContain(MIN_BRIDGE_SDK_VERSION); - }); - - it('row 5: protocol newer than Studio -> read-only with the newer-SDK warning', async () => { - const root = path.join(dir, 'ws'); - fs.mkdirSync(root, { recursive: true }); - sdkWorkspace(root); - const selector = new WorkspaceHostSelector( - refFor(root), - deps(root, { FAKE_BRIDGE_PROTOCOL: '2', FAKE_BRIDGE_SDK_VERSION: '99.0.0' }) - ); - const settled = await selector.ready(); - expect(settled.mode).toBe('read-only'); - expect(settled.banner).toEqual({ - tone: 'warning', - text: "This project's @lousho/build-ai-agent 99.0.0 is newer than this Studio understands. Update Studio to edit agents here.", - }); - }); - - it('row 6: a good hello -> bridge mode, no banner, hello sdkVersion wins', async () => { - const root = path.join(dir, 'ws'); - fs.mkdirSync(root, { recursive: true }); - sdkWorkspace(root); - const onChange = vi.fn(); - const selector = new WorkspaceHostSelector(refFor(root), deps(root, { FAKE_BRIDGE_SDK_VERSION: '8.8.8' })); - selector.onChange(onChange); - const settled = await selector.ready(); - expect(settled).toMatchObject({ mode: 'bridge', sdkVersion: '8.8.8', banner: null }); - expect(selector.current().mode).toBe('bridge'); - expect(onChange).toHaveBeenCalled(); - // The child env: observe target + workspace id + bridge flag, no studio token. - // The fake reports env on stderr after answering hello, so poll for it. - const deadline = Date.now() + 5_000; - let envLines: string[] = []; - while (Date.now() < deadline && envLines.length === 0) { - envLines = selector.client!.recentStderr().filter((l) => l.startsWith('env ')); - if (envLines.length === 0) await new Promise((r) => setTimeout(r, 50)); - } - try { - const env = Object.fromEntries(envLines.map((l) => l.slice(4).split(/=(.*)/s).slice(0, 2) as [string, string])); - expect(env.LOUSHO_OBSERVE).toBe('http://127.0.0.1:9'); - expect(env.LOUSHO_OBSERVE_PROJECT).toBe(refFor(root).id); - expect(env.LOUSHO_BRIDGE).toBe('1'); - expect(env.LOUSHO_STUDIO_TOKEN).toBe(''); - } finally { - await selector.client!.stop(); - } - }); - - it('row 7: handshake failure -> bridge mode with the restart banner', async () => { - const root = path.join(dir, 'ws'); - fs.mkdirSync(root, { recursive: true }); - sdkWorkspace(root); - const selector = new WorkspaceHostSelector( - refFor(root), - deps(root, { FAKE_BRIDGE_MODE: 'silent' }, { hello: 300 }) - ); - const settled = await selector.ready(); - expect(settled.mode).toBe('bridge'); - expect(settled.banner).toEqual({ - tone: 'danger', - text: "This project's agent process keeps stopping. Check the Studio terminal for its output, then restart it.", - action: 'restart', - }); - await selector.client!.stop(); - }); - - it('envCollector reads LOUSHO_OBSERVE_URL/TOKEN per call', () => { - const prevUrl = process.env.LOUSHO_OBSERVE_URL; - const prevToken = process.env.LOUSHO_OBSERVE_TOKEN; - try { - process.env.LOUSHO_OBSERVE_URL = 'http://127.0.0.1:9999/'; - process.env.LOUSHO_OBSERVE_TOKEN = 'tok'; - expect(envCollector()).toEqual({ url: 'http://127.0.0.1:9999', token: 'tok' }); - delete process.env.LOUSHO_OBSERVE_TOKEN; - expect(envCollector()).toEqual({ url: 'http://127.0.0.1:9999' }); - } finally { - if (prevUrl === undefined) delete process.env.LOUSHO_OBSERVE_URL; - else process.env.LOUSHO_OBSERVE_URL = prevUrl; - if (prevToken === undefined) delete process.env.LOUSHO_OBSERVE_TOKEN; - else process.env.LOUSHO_OBSERVE_TOKEN = prevToken; - } - }); -}); +/** + * W11b: `WorkspaceHostSelector` rows 1-7. Rows 1-2 decide from disk and + * never spawn; rows 3-7 run the real `BridgeClient` handshake against + * `fakeBridge.mjs` (installed as the workspace's `node_modules` SDK). + */ +import * as fs from 'node:fs'; +import * as os from 'node:os'; +import * as path from 'node:path'; +import { fileURLToPath } from 'node:url'; +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; +import type { WorkspaceRef } from '../../shared/workspace'; +import { BridgeAgentHost, HostReadOnlyError } from '../host/bridgeAgentHost'; +import { envCollector, WorkspaceHostSelector, type SelectHostDeps } from '../host/hostSelector'; +import { InProcessAgentHost } from '../host/inProcessAgentHost'; +import { FileCheckpointStore } from '../checkpointStore'; +import { FileApprovalStore } from '../approvalStore'; +import { MIN_BRIDGE_SDK_VERSION } from '../host/sdkCompat'; +import { workspaceRefFor } from '../workspace/workspaceId'; +import { SecretsStore } from '../secretsStore'; + +const FAKE_BRIDGE = fileURLToPath(new URL('../host/__tests__/fakeBridge.mjs', import.meta.url)); + +let dir: string; +let secretsDir: string; + +beforeEach(() => { + dir = fs.mkdtempSync(path.join(os.tmpdir(), 'lou-w11b-sel-')); + secretsDir = fs.mkdtempSync(path.join(os.tmpdir(), 'lou-w11b-sec-')); +}); + +afterEach(() => { + fs.rmSync(dir, { recursive: true, force: true }); + fs.rmSync(secretsDir, { recursive: true, force: true }); +}); + +/** A workspace whose node_modules SDK's `bin/lousho.js` IS the fake bridge. */ +function sdkWorkspace(root: string, sdkName = '@linuxdevil/lousho'): string { + const pkgDir = path.join(root, 'node_modules', ...sdkName.split('/')); + fs.mkdirSync(path.join(pkgDir, 'bin'), { recursive: true }); + fs.mkdirSync(path.join(pkgDir, 'dist', 'cli'), { recursive: true }); + fs.writeFileSync( + path.join(pkgDir, 'package.json'), + JSON.stringify({ name: sdkName, version: '9.9.9-test' }) + ); + fs.copyFileSync(FAKE_BRIDGE, path.join(pkgDir, 'bin', 'lousho.js')); + fs.writeFileSync(path.join(pkgDir, 'dist', 'cli', 'bridge.mjs'), '// built\n'); + return pkgDir; +} + +function deps(root: string, extraEnv: Record = {}, timeouts: Record = {}): SelectHostDeps { + return { + inProcess: () => + new InProcessAgentHost({ + baseDir: root, + checkpointStore: new FileCheckpointStore(root), + approvalStore: new FileApprovalStore(root), + }), + secretsStore: new SecretsStore(secretsDir), + collector: () => ({ url: 'http://127.0.0.1:9' }), + studioVersion: '9.9.9-studio', + clientOptions: { stderr: () => undefined, timeouts: { hello: 2_000, shutdown: 500, ...timeouts } }, + extraEnv: { FAKE_BRIDGE_REPORT_ENV: '1', ...extraEnv }, + }; +} + +const refFor = (root: string): WorkspaceRef => workspaceRefFor(root); + +describe('WorkspaceHostSelector', () => { + it('row 1: no SDK on disk -> in-process, info banner, no client, reports to observe', async () => { + const root = path.join(dir, 'plain'); + fs.mkdirSync(root, { recursive: true }); + const selector = new WorkspaceHostSelector(refFor(root), deps(root)); + expect(selector.host).toBeInstanceOf(InProcessAgentHost); + expect(selector.client).toBeUndefined(); + expect(selector.reportsToObserve).toBe(true); + expect(selector.current()).toEqual({ + mode: 'in-process', + sdkVersion: '9.9.9-studio', + banner: { + tone: 'info', + text: "This project has no @linuxdevil/lousho installed. Runs use Studio's built-in SDK 9.9.9-studio.", + }, + }); + await expect(selector.ready()).resolves.toMatchObject({ mode: 'in-process' }); + }); + + it('row 2: the SDK checkout itself, unbuilt -> in-process with the build hint', () => { + const root = path.join(dir, 'sdk-src'); + fs.mkdirSync(path.join(root, 'bin'), { recursive: true }); + fs.writeFileSync(path.join(root, 'package.json'), JSON.stringify({ name: 'lousho', version: '9.9.9' })); + fs.writeFileSync(path.join(root, 'bin', 'lousho.js'), '// bin\n'); + const selector = new WorkspaceHostSelector(refFor(root), deps(root)); + expect(selector.host).toBeInstanceOf(InProcessAgentHost); + expect(selector.client).toBeUndefined(); + expect(selector.current().banner).toEqual({ + tone: 'info', + text: "This SDK checkout is not built, so runs use Studio's built-in SDK 9.9.9-studio. Run npm run build to use it.", + }); + }); + + it('row 3: bridge child without the command -> read-only with the update warning', async () => { + const root = path.join(dir, 'ws'); + fs.mkdirSync(root, { recursive: true }); + sdkWorkspace(root); + const selector = new WorkspaceHostSelector(refFor(root), deps(root, { FAKE_BRIDGE_MODE: 'unknown-command' })); + expect(selector.host).toBeInstanceOf(BridgeAgentHost); + expect(selector.client).toBeDefined(); + expect(selector.reportsToObserve).toBe(false); + // Provisional snapshot before the handshake settles. + expect(selector.current()).toMatchObject({ mode: 'bridge', sdkVersion: '9.9.9-test', banner: null }); + const settled = await selector.ready(); + expect(settled.mode).toBe('read-only'); + expect(settled.banner).toEqual({ + tone: 'warning', + text: `Update @linuxdevil/lousho in this project to ${MIN_BRIDGE_SDK_VERSION} or later to edit agents here.`, + }); + await expect(selector.host.start({ agentId: 'a', spec: {} as never, sessionId: 's', input: 'hi', onEvent: () => {} })).rejects.toBeInstanceOf(HostReadOnlyError); + }); + + it('row 4: hello without spec v2 -> read-only with the same update warning', async () => { + const root = path.join(dir, 'ws'); + fs.mkdirSync(root, { recursive: true }); + sdkWorkspace(root); + const selector = new WorkspaceHostSelector(refFor(root), deps(root, { FAKE_BRIDGE_SPEC_VERSIONS: '[1]' })); + const settled = await selector.ready(); + expect(settled.mode).toBe('read-only'); + expect(settled.banner?.tone).toBe('warning'); + expect(settled.banner?.text).toContain(MIN_BRIDGE_SDK_VERSION); + }); + + it('row 5: protocol newer than Studio -> read-only with the newer-SDK warning', async () => { + const root = path.join(dir, 'ws'); + fs.mkdirSync(root, { recursive: true }); + sdkWorkspace(root); + const selector = new WorkspaceHostSelector( + refFor(root), + deps(root, { FAKE_BRIDGE_PROTOCOL: '2', FAKE_BRIDGE_SDK_VERSION: '99.0.0' }) + ); + const settled = await selector.ready(); + expect(settled.mode).toBe('read-only'); + expect(settled.banner).toEqual({ + tone: 'warning', + text: "This project's @linuxdevil/lousho 99.0.0 is newer than this Studio understands. Update Studio to edit agents here.", + }); + }); + + it('row 6: a good hello -> bridge mode, no banner, hello sdkVersion wins', async () => { + const root = path.join(dir, 'ws'); + fs.mkdirSync(root, { recursive: true }); + sdkWorkspace(root); + const onChange = vi.fn(); + const selector = new WorkspaceHostSelector(refFor(root), deps(root, { FAKE_BRIDGE_SDK_VERSION: '8.8.8' })); + selector.onChange(onChange); + const settled = await selector.ready(); + expect(settled).toMatchObject({ mode: 'bridge', sdkVersion: '8.8.8', banner: null }); + expect(selector.current().mode).toBe('bridge'); + expect(onChange).toHaveBeenCalled(); + // The child env: observe target + workspace id + bridge flag, no studio token. + // The fake reports env on stderr after answering hello, so poll for it. + const deadline = Date.now() + 5_000; + let envLines: string[] = []; + while (Date.now() < deadline && envLines.length === 0) { + envLines = selector.client!.recentStderr().filter((l) => l.startsWith('env ')); + if (envLines.length === 0) await new Promise((r) => setTimeout(r, 50)); + } + try { + const env = Object.fromEntries(envLines.map((l) => l.slice(4).split(/=(.*)/s).slice(0, 2) as [string, string])); + expect(env.LOUSHO_OBSERVE).toBe('http://127.0.0.1:9'); + expect(env.LOUSHO_OBSERVE_PROJECT).toBe(refFor(root).id); + expect(env.LOUSHO_BRIDGE).toBe('1'); + expect(env.LOUSHO_STUDIO_TOKEN).toBe(''); + } finally { + await selector.client!.stop(); + } + }); + + it('row 7: handshake failure -> bridge mode with the restart banner', async () => { + const root = path.join(dir, 'ws'); + fs.mkdirSync(root, { recursive: true }); + sdkWorkspace(root); + const selector = new WorkspaceHostSelector( + refFor(root), + deps(root, { FAKE_BRIDGE_MODE: 'silent' }, { hello: 300 }) + ); + const settled = await selector.ready(); + expect(settled.mode).toBe('bridge'); + expect(settled.banner).toEqual({ + tone: 'danger', + text: "This project's agent process keeps stopping. Check the Studio terminal for its output, then restart it.", + action: 'restart', + }); + await selector.client!.stop(); + }); + + it('envCollector reads LOUSHO_OBSERVE_URL/TOKEN per call', () => { + const prevUrl = process.env.LOUSHO_OBSERVE_URL; + const prevToken = process.env.LOUSHO_OBSERVE_TOKEN; + try { + process.env.LOUSHO_OBSERVE_URL = 'http://127.0.0.1:9999/'; + process.env.LOUSHO_OBSERVE_TOKEN = 'tok'; + expect(envCollector()).toEqual({ url: 'http://127.0.0.1:9999', token: 'tok' }); + delete process.env.LOUSHO_OBSERVE_TOKEN; + expect(envCollector()).toEqual({ url: 'http://127.0.0.1:9999' }); + } finally { + if (prevUrl === undefined) delete process.env.LOUSHO_OBSERVE_URL; + else process.env.LOUSHO_OBSERVE_URL = prevUrl; + if (prevToken === undefined) delete process.env.LOUSHO_OBSERVE_TOKEN; + else process.env.LOUSHO_OBSERVE_TOKEN = prevToken; + } + }); +}); diff --git a/apps/agent-forge/server/__tests__/idleVersions.test.ts b/apps/agent-forge/server/__tests__/idleVersions.test.ts index f69774b66..d765f62c3 100644 --- a/apps/agent-forge/server/__tests__/idleVersions.test.ts +++ b/apps/agent-forge/server/__tests__/idleVersions.test.ts @@ -3,7 +3,7 @@ import * as fs from 'node:fs'; import * as os from 'node:os'; import * as path from 'node:path'; import request from 'supertest'; -import type { AgentSpec } from 'lousho'; +import type { AgentSpec } from '@linuxdevil/lousho'; import { createApp } from '../app'; import { startStudioServer } from '../index'; import { STUDIO_TOKEN_HEADER } from '../auth'; diff --git a/apps/agent-forge/server/__tests__/importRoutes.test.ts b/apps/agent-forge/server/__tests__/importRoutes.test.ts index 2babe3844..02ee3dc2f 100644 --- a/apps/agent-forge/server/__tests__/importRoutes.test.ts +++ b/apps/agent-forge/server/__tests__/importRoutes.test.ts @@ -10,7 +10,7 @@ import * as fs from 'node:fs'; import * as os from 'node:os'; import * as path from 'node:path'; import request from 'supertest'; -import type { AgentSpecV2 } from 'lousho'; +import type { AgentSpecV2 } from '@linuxdevil/lousho'; import { createApp } from '../app'; import { RunManager } from '../runRegistry'; import { newTestHost } from './testHost'; diff --git a/apps/agent-forge/server/__tests__/inProcessAgentHost.test.ts b/apps/agent-forge/server/__tests__/inProcessAgentHost.test.ts index d05b1f702..3ba1cb967 100644 --- a/apps/agent-forge/server/__tests__/inProcessAgentHost.test.ts +++ b/apps/agent-forge/server/__tests__/inProcessAgentHost.test.ts @@ -10,7 +10,7 @@ import { describe, it, expect, beforeEach, afterEach } from 'vitest'; import * as fs from 'node:fs'; import * as path from 'node:path'; import * as os from 'node:os'; -import type { AgentSpecV2 } from 'lousho'; +import type { AgentSpecV2 } from '@linuxdevil/lousho'; import { InProcessAgentHost } from '../host/inProcessAgentHost'; import { HostRunNotActiveError, diff --git a/apps/agent-forge/server/__tests__/inboxRoutes.test.ts b/apps/agent-forge/server/__tests__/inboxRoutes.test.ts index c215bf658..8deb96ba5 100644 --- a/apps/agent-forge/server/__tests__/inboxRoutes.test.ts +++ b/apps/agent-forge/server/__tests__/inboxRoutes.test.ts @@ -3,7 +3,7 @@ import * as fs from 'node:fs'; import * as os from 'node:os'; import * as path from 'node:path'; import request from 'supertest'; -import type { AgentSpec } from 'lousho'; +import type { AgentSpec } from '@linuxdevil/lousho'; import { createApp } from '../app'; import { RunManager } from '../runRegistry'; import { newTestHost } from './testHost'; diff --git a/apps/agent-forge/server/__tests__/judge.test.ts b/apps/agent-forge/server/__tests__/judge.test.ts index d268e407b..a2d9def8c 100644 --- a/apps/agent-forge/server/__tests__/judge.test.ts +++ b/apps/agent-forge/server/__tests__/judge.test.ts @@ -12,7 +12,7 @@ import * as os from 'node:os'; import * as http from 'node:http'; import { once } from 'node:events'; import request from 'supertest'; -import { startObserveDaemon, type ObserveDaemon } from 'lousho/observe'; +import { startObserveDaemon, type ObserveDaemon } from '@linuxdevil/lousho/observe'; import { createApp } from '../app'; import { judgePrompt, parseJudgeReply, startJudgeLoop, type JudgeFn, type JudgeLoop, type JudgeLoopOptions } from '../judge'; import { RunManager } from '../runRegistry'; diff --git a/apps/agent-forge/server/__tests__/localTools.test.ts b/apps/agent-forge/server/__tests__/localTools.test.ts index 3570349c8..965eb0c30 100644 --- a/apps/agent-forge/server/__tests__/localTools.test.ts +++ b/apps/agent-forge/server/__tests__/localTools.test.ts @@ -12,7 +12,7 @@ import { describe, it, expect, beforeEach, afterEach } from 'vitest'; import * as fs from 'node:fs'; import * as path from 'node:path'; import * as os from 'node:os'; -import type { AgentSpecV2 } from 'lousho'; +import type { AgentSpecV2 } from '@linuxdevil/lousho'; import { RunManager } from '../runRegistry'; import { newTestHost } from './testHost'; import { FileCheckpointStore } from '../checkpointStore'; diff --git a/apps/agent-forge/server/__tests__/mcpProbe.test.ts b/apps/agent-forge/server/__tests__/mcpProbe.test.ts index d2a2fde0c..7ab780a2e 100644 --- a/apps/agent-forge/server/__tests__/mcpProbe.test.ts +++ b/apps/agent-forge/server/__tests__/mcpProbe.test.ts @@ -4,8 +4,8 @@ import * as os from 'node:os'; import * as path from 'node:path'; import express, { type Express } from 'express'; import request from 'supertest'; -import type { NamedToolDescriptor } from 'lousho'; -import type { connectMcp, McpConnections } from 'lousho/mcp'; +import type { NamedToolDescriptor } from '@linuxdevil/lousho'; +import type { connectMcp, McpConnections } from '@linuxdevil/lousho/mcp'; import { createApp } from '../app'; import { STUDIO_TOKEN_HEADER } from '../auth'; import { registerMcpProbeRoute } from '../routes/mcpProbe'; diff --git a/apps/agent-forge/server/__tests__/missingProviderKey.test.ts b/apps/agent-forge/server/__tests__/missingProviderKey.test.ts index 006ca4afd..8b5926dff 100644 --- a/apps/agent-forge/server/__tests__/missingProviderKey.test.ts +++ b/apps/agent-forge/server/__tests__/missingProviderKey.test.ts @@ -3,7 +3,7 @@ import * as fs from 'node:fs'; import * as path from 'node:path'; import * as os from 'node:os'; import request from 'supertest'; -import type { AgentSpec } from 'lousho'; +import type { AgentSpec } from '@linuxdevil/lousho'; import { createApp } from '../app'; import { RunManager } from '../runRegistry'; import { newTestHost } from './testHost'; diff --git a/apps/agent-forge/server/__tests__/multiWorkspace.test.ts b/apps/agent-forge/server/__tests__/multiWorkspace.test.ts index 668ac8658..f15203b8f 100644 --- a/apps/agent-forge/server/__tests__/multiWorkspace.test.ts +++ b/apps/agent-forge/server/__tests__/multiWorkspace.test.ts @@ -3,7 +3,7 @@ import * as fs from 'node:fs'; import * as os from 'node:os'; import * as path from 'node:path'; import request from 'supertest'; -import type { AgentSpec, AgentSpecV2 } from 'lousho'; +import type { AgentSpec, AgentSpecV2 } from '@linuxdevil/lousho'; import { createApp } from '../app'; import type { Express } from 'express'; import { createWorkspaceContext, type WorkspaceContext, type WorkspaceServices } from '../workspace/context'; diff --git a/apps/agent-forge/server/__tests__/multiWorkspaceWs.test.ts b/apps/agent-forge/server/__tests__/multiWorkspaceWs.test.ts index 80e1248c6..58e59a197 100644 --- a/apps/agent-forge/server/__tests__/multiWorkspaceWs.test.ts +++ b/apps/agent-forge/server/__tests__/multiWorkspaceWs.test.ts @@ -4,7 +4,7 @@ import * as os from 'node:os'; import * as path from 'node:path'; import request from 'supertest'; import { WebSocket } from 'ws'; -import type { AgentSpecV2 } from 'lousho'; +import type { AgentSpecV2 } from '@linuxdevil/lousho'; import { startStudioServer, type StudioServerHandle } from '../index'; import { deferred, resetSteerGate, setSteerGateScript } from './steerGate'; diff --git a/apps/agent-forge/server/__tests__/noSdkSource.test.ts b/apps/agent-forge/server/__tests__/noSdkSource.test.ts index b2dbe7358..18c838b00 100644 --- a/apps/agent-forge/server/__tests__/noSdkSource.test.ts +++ b/apps/agent-forge/server/__tests__/noSdkSource.test.ts @@ -1,6 +1,6 @@ /** * W14 (PLAN §19.7): Studio imports the SDK through its package exports only. - * Exercises the `lousho/no-sdk-source` ESLint rule the `lint:imports` gate runs. + * Exercises the `@linuxdevil/lousho/no-sdk-source` ESLint rule the `lint:imports` gate runs. */ import { RuleTester } from 'eslint'; import tseslint from 'typescript-eslint'; @@ -18,14 +18,14 @@ const appFile = (rel: string) => path.join(APP, rel); new RuleTester({ languageOptions: { parser: tseslint.parser } }).run('no-sdk-source', noSdkSource, { valid: [ - { code: "import { AgentExecutor } from 'lousho';", filename: appFile('server/x.ts') }, - { code: "import { agentSpecSchema } from 'lousho/spec';", filename: appFile('src/x.ts') }, - { code: "import { startObserveDaemon } from 'lousho/observe';", filename: appFile('server/__tests__/x.test.ts') }, - { code: "import type { ToolRef } from 'lousho/spec';", filename: appFile('src/graph/v2/specTypes.ts') }, + { code: "import { AgentExecutor } from '@linuxdevil/lousho';", filename: appFile('server/x.ts') }, + { code: "import { agentSpecSchema } from '@linuxdevil/lousho/spec';", filename: appFile('src/x.ts') }, + { code: "import { startObserveDaemon } from '@linuxdevil/lousho/observe';", filename: appFile('server/__tests__/x.test.ts') }, + { code: "import type { ToolRef } from '@linuxdevil/lousho/spec';", filename: appFile('src/graph/v2/specTypes.ts') }, { code: "import { helper } from '../../state/AppState';", filename: appFile('src/screens/x.ts') }, { code: "import { helper } from './local';", filename: appFile('server/x.ts') }, { code: "vi.mock('../../state/AppState');", filename: appFile('src/screens/x.test.ts') }, - { code: "vi.mock('lousho/spec', () => ({}));", filename: appFile('src/x.test.ts') }, + { code: "vi.mock('@linuxdevil/lousho/spec', () => ({}));", filename: appFile('src/x.test.ts') }, // In-app relative specifiers that stay inside apps/agent-forge are fine. { code: "import { graphToSpec } from '../../src/graph/graphToSpec';", filename: appFile('server/__tests__/x.test.ts') }, ], @@ -41,7 +41,7 @@ new RuleTester({ languageOptions: { parser: tseslint.parser } }).run('no-sdk-sou errors: [{ messageId: 'deep' }], }, { - code: "import { x } from 'lousho/src/spec/schema';", + code: "import { x } from '@linuxdevil/lousho/src/spec/schema';", filename: appFile('src/x.ts'), errors: [{ messageId: 'deep' }], }, diff --git a/apps/agent-forge/server/__tests__/nodeFrames.test.ts b/apps/agent-forge/server/__tests__/nodeFrames.test.ts index abc237859..3b14e9a4e 100644 --- a/apps/agent-forge/server/__tests__/nodeFrames.test.ts +++ b/apps/agent-forge/server/__tests__/nodeFrames.test.ts @@ -13,7 +13,7 @@ import { emptyRunUsage, type AgentSpecV2, type Span, -} from 'lousho'; +} from '@linuxdevil/lousho'; import { RunManager } from '../runRegistry'; import { newTestHost } from './testHost'; import { attachWebSocketServer } from '../wsServer'; diff --git a/apps/agent-forge/server/__tests__/nodeStates.test.ts b/apps/agent-forge/server/__tests__/nodeStates.test.ts index ff2b4f820..b5c147164 100644 --- a/apps/agent-forge/server/__tests__/nodeStates.test.ts +++ b/apps/agent-forge/server/__tests__/nodeStates.test.ts @@ -1,5 +1,5 @@ import { describe, it, expect } from 'vitest'; -import { APPROVAL_WAIT_SPAN_NAME, SdkAttr, type Span } from 'lousho'; +import { APPROVAL_WAIT_SPAN_NAME, SdkAttr, type Span } from '@linuxdevil/lousho'; import { nodeStateFor } from '../nodeStates'; const NOW = new Date('2026-10-29T12:00:00.000Z'); diff --git a/apps/agent-forge/server/__tests__/observeReporting.test.ts b/apps/agent-forge/server/__tests__/observeReporting.test.ts index 553c7587e..2538a7b12 100644 --- a/apps/agent-forge/server/__tests__/observeReporting.test.ts +++ b/apps/agent-forge/server/__tests__/observeReporting.test.ts @@ -4,7 +4,7 @@ * content is captured only for a loopback target. */ import { describe, it, expect, afterEach } from 'vitest'; -import { observeExporterFromEnv, type ObserveReporter } from 'lousho/traces'; +import { observeExporterFromEnv, type ObserveReporter } from '@linuxdevil/lousho/traces'; import { isLoopbackUrl, observeReporting } from '../observeReporting'; const noFetch = (async () => new Response('{}', { status: 200 })) as typeof fetch; diff --git a/apps/agent-forge/server/__tests__/pinSdkRange.test.ts b/apps/agent-forge/server/__tests__/pinSdkRange.test.ts new file mode 100644 index 000000000..7919acbf9 --- /dev/null +++ b/apps/agent-forge/server/__tests__/pinSdkRange.test.ts @@ -0,0 +1,112 @@ +/** + * `scripts/pin-sdk-range.mjs` - the pack-time rewrite of the SDK dependency + * from `file:../..` to `^`, exercised against a temp monorepo + * layout (`/apps/x` + `/package.json`). + */ +import { describe, expect, it } from 'vitest'; +import { spawnSync } from 'node:child_process'; +import * as fs from 'node:fs'; +import * as os from 'node:os'; +import * as path from 'node:path'; +import { fileURLToPath } from 'node:url'; + +const SCRIPT = path.resolve( + path.dirname(fileURLToPath(import.meta.url)), + '..', + '..', + 'scripts', + 'pin-sdk-range.mjs' +); + +interface Fixture { + root: string; + app: string; + pkgPath: string; + backupPath: string; +} + +function makeFixture({ withSdkRoot = true }: { withSdkRoot?: boolean } = {}): Fixture { + const root = fs.mkdtempSync(path.join(os.tmpdir(), 'lousho-pin-')); + const app = path.join(root, 'apps', 'x'); + fs.mkdirSync(path.join(app, 'scripts'), { recursive: true }); + fs.copyFileSync(SCRIPT, path.join(app, 'scripts', 'pin-sdk-range.mjs')); + if (withSdkRoot) { + fs.writeFileSync( + path.join(root, 'package.json'), + `${JSON.stringify({ name: '@linuxdevil/lousho', version: '9.9.9' }, null, 2)}\n` + ); + } + const pkgPath = path.join(app, 'package.json'); + fs.writeFileSync( + pkgPath, + `${JSON.stringify( + { + name: '@linuxdevil/studio', + version: '0.1.0-alpha.0', + dependencies: { '@linuxdevil/lousho': 'file:../..', yaml: '^2.9.1' }, + }, + null, + 2 + )}\n` + ); + return { root, app, pkgPath, backupPath: path.join(app, '.package.json.prepack') }; +} + +function run(script: string, args: string[], cwd: string) { + return spawnSync(process.execPath, [script, ...args], { cwd, encoding: 'utf8' }); +} + +describe('pin-sdk-range.mjs', () => { + it('pin rewrites the SDK dep to ^, keeps a backup, refuses a second pin, restore puts it back', () => { + const fx = makeFixture(); + try { + const original = fs.readFileSync(fx.pkgPath, 'utf8'); + const pin = run('scripts/pin-sdk-range.mjs', ['pin'], fx.app); + expect(pin.status).toBe(0); + const pinned = JSON.parse(fs.readFileSync(fx.pkgPath, 'utf8')) as { + dependencies: Record; + }; + expect(pinned.dependencies['@linuxdevil/lousho']).toBe('^9.9.9'); + expect(pinned.dependencies.yaml).toBe('^2.9.1'); + expect(fs.existsSync(fx.backupPath)).toBe(true); + expect(fs.readFileSync(fx.backupPath, 'utf8')).toBe(original); + + const second = run('scripts/pin-sdk-range.mjs', ['pin'], fx.app); + expect(second.status).toBe(1); + expect(second.stderr).toContain('restore first'); + + const restore = run('scripts/pin-sdk-range.mjs', ['restore'], fx.app); + expect(restore.status).toBe(0); + expect(fs.readFileSync(fx.pkgPath, 'utf8')).toBe(original); + expect(fs.existsSync(fx.backupPath)).toBe(false); + } finally { + fs.rmSync(fx.root, { recursive: true, force: true }); + } + }); + + it('pin is a no-op when there is no SDK root package', () => { + const fx = makeFixture({ withSdkRoot: false }); + try { + const original = fs.readFileSync(fx.pkgPath, 'utf8'); + const pin = run('scripts/pin-sdk-range.mjs', ['pin'], fx.app); + expect(pin.status).toBe(0); + expect(fs.readFileSync(fx.pkgPath, 'utf8')).toBe(original); + expect(fs.existsSync(fx.backupPath)).toBe(false); + } finally { + fs.rmSync(fx.root, { recursive: true, force: true }); + } + }); + + it('pin is a no-op when the root package is not the SDK', () => { + const fx = makeFixture(); + try { + fs.writeFileSync(path.join(fx.root, 'package.json'), JSON.stringify({ name: 'other', version: '1.0.0' })); + const original = fs.readFileSync(fx.pkgPath, 'utf8'); + const pin = run('scripts/pin-sdk-range.mjs', ['pin'], fx.app); + expect(pin.status).toBe(0); + expect(fs.readFileSync(fx.pkgPath, 'utf8')).toBe(original); + } finally { + fs.rmSync(fx.root, { recursive: true, force: true }); + } + }); +}); diff --git a/apps/agent-forge/server/__tests__/pins.test.ts b/apps/agent-forge/server/__tests__/pins.test.ts index 943f44d53..2bc291e21 100644 --- a/apps/agent-forge/server/__tests__/pins.test.ts +++ b/apps/agent-forge/server/__tests__/pins.test.ts @@ -2,7 +2,7 @@ import { describe, it, expect, beforeEach, afterEach } from 'vitest'; import * as fs from 'node:fs'; import * as path from 'node:path'; import { fileURLToPath } from 'node:url'; -import type { AgentSpecV2, ToolCallHookContext } from 'lousho'; +import type { AgentSpecV2, ToolCallHookContext } from '@linuxdevil/lousho'; import { MAX_PINS, pinsProblem, pinnedResultsHook } from '../pins'; import { RunManager } from '../runRegistry'; import { newTestHost } from './testHost'; @@ -87,7 +87,7 @@ describe('pinnedResultsHook', () => { describe('RunManager run with pins', () => { // The `{ file }` tool ref resolves under the SpecHost baseDir - // (`/.lousho/agents`), and the tool file imports 'lousho'/'zod' - + // (`/.lousho/agents`), and the tool file imports '@linuxdevil/lousho'/'zod' - // so baseDir must live inside this repo for Node's resolution to find // them. `.tmp-pins-*` dirs are removed in afterEach. const repoRoot = fileURLToPath(new URL('../../../../', import.meta.url)); @@ -111,7 +111,7 @@ describe('RunManager run with pins', () => { [ "import { appendFileSync } from 'node:fs';", "import { z } from 'zod';", - "import { defineTool } from 'lousho';", + "import { defineTool } from '@linuxdevil/lousho';", `const MARKER = ${JSON.stringify(markerPath)};`, 'export default defineTool({', " name: 'spy-tool',", diff --git a/apps/agent-forge/server/__tests__/playground.test.ts b/apps/agent-forge/server/__tests__/playground.test.ts index ffe6e729d..e0bdc7822 100644 --- a/apps/agent-forge/server/__tests__/playground.test.ts +++ b/apps/agent-forge/server/__tests__/playground.test.ts @@ -4,7 +4,7 @@ import * as path from 'node:path'; import * as os from 'node:os'; import * as http from 'node:http'; import type { AddressInfo } from 'node:net'; -import { LLMProviderRegistry, createMockProvider, type AgentEvent } from 'lousho'; +import { LLMProviderRegistry, createMockProvider, type AgentEvent } from '@linuxdevil/lousho'; import request from 'supertest'; import { createApp } from '../app'; import { RunManager } from '../runRegistry'; diff --git a/apps/agent-forge/server/__tests__/portabilityRoutes.test.ts b/apps/agent-forge/server/__tests__/portabilityRoutes.test.ts index 618d2baca..316ea0fae 100644 --- a/apps/agent-forge/server/__tests__/portabilityRoutes.test.ts +++ b/apps/agent-forge/server/__tests__/portabilityRoutes.test.ts @@ -11,7 +11,7 @@ import * as os from 'node:os'; import * as path from 'node:path'; import { parse as parseYaml } from 'yaml'; import request from 'supertest'; -import type { AgentSpecV2 } from 'lousho'; +import type { AgentSpecV2 } from '@linuxdevil/lousho'; import { createApp } from '../app'; import { RunManager } from '../runRegistry'; import { newTestHost } from './testHost'; diff --git a/apps/agent-forge/server/__tests__/publishRoute.test.ts b/apps/agent-forge/server/__tests__/publishRoute.test.ts index fb0e85dad..0b54c51a5 100644 --- a/apps/agent-forge/server/__tests__/publishRoute.test.ts +++ b/apps/agent-forge/server/__tests__/publishRoute.test.ts @@ -3,7 +3,7 @@ import * as fs from 'node:fs'; import * as os from 'node:os'; import * as path from 'node:path'; import request from 'supertest'; -import type { AgentSpecV2 } from 'lousho'; +import type { AgentSpecV2 } from '@linuxdevil/lousho'; import type { AgentRecord } from '../../shared/wireTypes'; import { createApp } from '../app'; import { RunManager } from '../runRegistry'; diff --git a/apps/agent-forge/server/__tests__/questions.test.ts b/apps/agent-forge/server/__tests__/questions.test.ts index 627d6713e..dcafaabfe 100644 --- a/apps/agent-forge/server/__tests__/questions.test.ts +++ b/apps/agent-forge/server/__tests__/questions.test.ts @@ -17,8 +17,8 @@ import * as fs from 'node:fs'; import * as path from 'node:path'; import * as os from 'node:os'; import request from 'supertest'; -import { LLMProviderRegistry, type AgentSpecV2 } from 'lousho'; -import { mockModel, type MockTurn } from 'lousho/testing'; +import { LLMProviderRegistry, type AgentSpecV2 } from '@linuxdevil/lousho'; +import { mockModel, type MockTurn } from '@linuxdevil/lousho/testing'; import type { LogEntry } from '../../shared/wireTypes'; import { createApp } from '../app'; import { RunManager } from '../runRegistry'; diff --git a/apps/agent-forge/server/__tests__/ratings.test.ts b/apps/agent-forge/server/__tests__/ratings.test.ts index 6ae784ac8..e56bbb164 100644 --- a/apps/agent-forge/server/__tests__/ratings.test.ts +++ b/apps/agent-forge/server/__tests__/ratings.test.ts @@ -3,7 +3,7 @@ import * as fs from 'node:fs'; import * as os from 'node:os'; import * as path from 'node:path'; import request from 'supertest'; -import type { AgentSpec } from 'lousho'; +import type { AgentSpec } from '@linuxdevil/lousho'; import { createApp } from '../app'; import { RunManager } from '../runRegistry'; import { newTestHost } from './testHost'; diff --git a/apps/agent-forge/server/__tests__/runRegistry.test.ts b/apps/agent-forge/server/__tests__/runRegistry.test.ts index e5ea509ab..ee4cd789c 100644 --- a/apps/agent-forge/server/__tests__/runRegistry.test.ts +++ b/apps/agent-forge/server/__tests__/runRegistry.test.ts @@ -5,7 +5,7 @@ import * as os from 'node:os'; import * as http from 'node:http'; import type { AddressInfo } from 'node:net'; import { WebSocket } from 'ws'; -import { upgradeSpec, type AgentSpec, type AgentSpecV2 } from 'lousho'; +import { upgradeSpec, type AgentSpec, type AgentSpecV2 } from '@linuxdevil/lousho'; import { RunManager } from '../runRegistry'; import { newTestHost } from './testHost'; import { deferred, resetSteerGate, setSteerGateScript } from './steerGate'; diff --git a/apps/agent-forge/server/__tests__/runSource.test.ts b/apps/agent-forge/server/__tests__/runSource.test.ts index aa6e52668..e36bac670 100644 --- a/apps/agent-forge/server/__tests__/runSource.test.ts +++ b/apps/agent-forge/server/__tests__/runSource.test.ts @@ -2,7 +2,7 @@ import { describe, it, expect, beforeEach, afterEach } from 'vitest'; import * as fs from 'node:fs'; import * as path from 'node:path'; import * as os from 'node:os'; -import type { AgentSpecV2 } from 'lousho'; +import type { AgentSpecV2 } from '@linuxdevil/lousho'; import type { AgentRunStatusPayload, RunStatus } from '../../shared/wireTypes'; import { RunManager } from '../runRegistry'; import { newTestHost } from './testHost'; diff --git a/apps/agent-forge/server/__tests__/sdkRoot.test.ts b/apps/agent-forge/server/__tests__/sdkRoot.test.ts new file mode 100644 index 000000000..35309f83c --- /dev/null +++ b/apps/agent-forge/server/__tests__/sdkRoot.test.ts @@ -0,0 +1,61 @@ +/** + * `sdkRootDir()` must find the SDK both from the monorepo (workspace + * symlink) and from an installed `node_modules/@linuxdevil/studio` layout. + */ +import { afterEach, describe, expect, it } from 'vitest'; +import * as fs from 'node:fs'; +import * as os from 'node:os'; +import * as path from 'node:path'; +import { sdkRootDir } from '../sdkRoot'; + +const SDK_PACKAGE = '@linuxdevil/lousho'; +let tmp: string | undefined; + +afterEach(() => { + delete process.env.LOUSHO_SDK_ROOT; + if (tmp) fs.rmSync(tmp, { recursive: true, force: true }); + tmp = undefined; +}); + +function pkgName(dir: string): string | undefined { + const pkgPath = path.join(dir, 'package.json'); + return fs.existsSync(pkgPath) ? (JSON.parse(fs.readFileSync(pkgPath, 'utf8')) as { name?: string }).name : undefined; +} + +describe('sdkRootDir', () => { + it('resolves the monorepo checkout to the SDK package root', () => { + const root = sdkRootDir(); + expect(pkgName(root)).toBe(SDK_PACKAGE); + expect(fs.existsSync(path.join(root, 'registry', 'dist', 'index.json'))).toBe(true); + }); + + it('honours LOUSHO_SDK_ROOT', () => { + process.env.LOUSHO_SDK_ROOT = '/custom/sdk'; + expect(sdkRootDir()).toBe('/custom/sdk'); + }); + + it('finds the SDK from an installed node_modules/@linuxdevil/studio layout', () => { + tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'lousho-sdkroot-')); + const sdkDir = path.join(tmp, 'node_modules', '@linuxdevil', 'lousho'); + const studioDir = path.join(tmp, 'node_modules', '@linuxdevil', 'studio'); + fs.mkdirSync(path.join(sdkDir, 'dist'), { recursive: true }); + fs.mkdirSync(path.join(studioDir, 'dist-server'), { recursive: true }); + fs.writeFileSync( + path.join(sdkDir, 'package.json'), + JSON.stringify({ name: SDK_PACKAGE, version: '1.0.0', main: 'dist/index.js' }) + ); + fs.writeFileSync(path.join(sdkDir, 'dist', 'index.js'), 'module.exports = {};\n'); + const moduleDir = path.join(studioDir, 'dist-server'); + expect(sdkRootDir(moduleDir)).toBe(sdkDir); + }); + + it('throws a clear error when the SDK cannot be resolved', () => { + tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'lousho-sdkroot-')); + const moduleDir = path.join(tmp, 'dist-server'); + fs.mkdirSync(moduleDir, { recursive: true }); + // A temp dir outside any node_modules tree: resolution walks up to the + // filesystem root without finding the package (unless the machine has a + // global install, which the env override covers in real use). + expect(() => sdkRootDir(moduleDir)).toThrow(/could not locate the @linuxdevil\/lousho package/); + }); +}); diff --git a/apps/agent-forge/server/__tests__/steer.test.ts b/apps/agent-forge/server/__tests__/steer.test.ts index fdf475c51..c1a5d1bd2 100644 --- a/apps/agent-forge/server/__tests__/steer.test.ts +++ b/apps/agent-forge/server/__tests__/steer.test.ts @@ -17,7 +17,7 @@ import { describe, it, expect, beforeEach, afterEach } from 'vitest'; import * as fs from 'node:fs'; import * as path from 'node:path'; import * as os from 'node:os'; -import { type AgentSpecV2 } from 'lousho'; +import { type AgentSpecV2 } from '@linuxdevil/lousho'; import { RunManager, NoActiveRunError, SteerUnsupportedError } from '../runRegistry'; import { newTestHost } from './testHost'; import { FileCheckpointStore } from '../checkpointStore'; diff --git a/apps/agent-forge/server/__tests__/steerGate.ts b/apps/agent-forge/server/__tests__/steerGate.ts index 0ecefe7ad..173e5872f 100644 --- a/apps/agent-forge/server/__tests__/steerGate.ts +++ b/apps/agent-forge/server/__tests__/steerGate.ts @@ -15,7 +15,7 @@ import { type LLMProvider, type StreamChunk, type StreamResult, -} from 'lousho'; +} from '@linuxdevil/lousho'; export function deferred(): { promise: Promise; resolve: () => void } { let resolve!: () => void; diff --git a/apps/agent-forge/server/__tests__/suggestionRoutes.test.ts b/apps/agent-forge/server/__tests__/suggestionRoutes.test.ts index 81244ef85..bdb39fa7a 100644 --- a/apps/agent-forge/server/__tests__/suggestionRoutes.test.ts +++ b/apps/agent-forge/server/__tests__/suggestionRoutes.test.ts @@ -3,7 +3,7 @@ import * as fs from 'node:fs'; import * as os from 'node:os'; import * as path from 'node:path'; import request from 'supertest'; -import type { AgentSpec, AgentSpecV2 } from 'lousho'; +import type { AgentSpec, AgentSpecV2 } from '@linuxdevil/lousho'; import { createApp } from '../app'; import { RunManager } from '../runRegistry'; import { newTestHost } from './testHost'; diff --git a/apps/agent-forge/server/__tests__/timeTravel.test.ts b/apps/agent-forge/server/__tests__/timeTravel.test.ts index 39dea2dc0..27c7f6685 100644 --- a/apps/agent-forge/server/__tests__/timeTravel.test.ts +++ b/apps/agent-forge/server/__tests__/timeTravel.test.ts @@ -8,7 +8,7 @@ import * as fs from 'node:fs'; import * as path from 'node:path'; import * as os from 'node:os'; import request from 'supertest'; -import type { AgentSpec } from 'lousho'; +import type { AgentSpec } from '@linuxdevil/lousho'; import { createApp } from '../app'; import { RunManager } from '../runRegistry'; import { newTestHost } from './testHost'; diff --git a/apps/agent-forge/server/__tests__/traces.test.ts b/apps/agent-forge/server/__tests__/traces.test.ts index ec4bb2b98..2f0dbd1db 100644 --- a/apps/agent-forge/server/__tests__/traces.test.ts +++ b/apps/agent-forge/server/__tests__/traces.test.ts @@ -7,8 +7,8 @@ import * as fs from 'node:fs'; import * as path from 'node:path'; import * as os from 'node:os'; import request from 'supertest'; -import { listTraces } from 'lousho/traces'; -import type { AgentSpec } from 'lousho'; +import { listTraces } from '@linuxdevil/lousho/traces'; +import type { AgentSpec } from '@linuxdevil/lousho'; import { createApp } from '../app'; import { RunManager } from '../runRegistry'; import { newTestHost } from './testHost'; diff --git a/apps/agent-forge/server/__tests__/triggerHost.test.ts b/apps/agent-forge/server/__tests__/triggerHost.test.ts index 697787c11..3ce927fa8 100644 --- a/apps/agent-forge/server/__tests__/triggerHost.test.ts +++ b/apps/agent-forge/server/__tests__/triggerHost.test.ts @@ -2,8 +2,8 @@ import { EventEmitter } from 'node:events'; import { afterEach, describe, expect, it, vi } from 'vitest'; -import type { AgentSpecV2 } from 'lousho'; -import type { TriggerSpec } from 'lousho/spec'; +import type { AgentSpecV2 } from '@linuxdevil/lousho'; +import type { TriggerSpec } from '@linuxdevil/lousho/spec'; import type { AgentRepo } from '../storage/agentRepo'; import { AlreadyRunningError, type RunManager } from '../runRegistry'; diff --git a/apps/agent-forge/server/__tests__/versionRoutes.test.ts b/apps/agent-forge/server/__tests__/versionRoutes.test.ts index 0b85f0ef8..af3afd2b7 100644 --- a/apps/agent-forge/server/__tests__/versionRoutes.test.ts +++ b/apps/agent-forge/server/__tests__/versionRoutes.test.ts @@ -4,7 +4,7 @@ import * as os from 'node:os'; import * as path from 'node:path'; import request from 'supertest'; import { stringify as stringifyYaml } from 'yaml'; -import type { AgentSpec } from 'lousho'; +import type { AgentSpec } from '@linuxdevil/lousho'; import { createApp } from '../app'; import { StudioEventBus } from '../events/eventBus'; import { RunManager } from '../runRegistry'; diff --git a/apps/agent-forge/server/__tests__/workspaceScanRoute.test.ts b/apps/agent-forge/server/__tests__/workspaceScanRoute.test.ts index 4c65143f5..2379455bd 100644 --- a/apps/agent-forge/server/__tests__/workspaceScanRoute.test.ts +++ b/apps/agent-forge/server/__tests__/workspaceScanRoute.test.ts @@ -10,7 +10,7 @@ import * as fs from 'node:fs'; import * as os from 'node:os'; import * as path from 'node:path'; import request from 'supertest'; -import type { AgentSpecV2 } from 'lousho'; +import type { AgentSpecV2 } from '@linuxdevil/lousho'; import type { Express } from 'express'; import { createApp } from '../app'; import { STUDIO_TOKEN_HEADER } from '../auth'; diff --git a/apps/agent-forge/server/__tests__/workspaceSdk.test.ts b/apps/agent-forge/server/__tests__/workspaceSdk.test.ts index 27849019b..3a4002e45 100644 --- a/apps/agent-forge/server/__tests__/workspaceSdk.test.ts +++ b/apps/agent-forge/server/__tests__/workspaceSdk.test.ts @@ -1,101 +1,101 @@ -/** - * W11b: `findWorkspaceSdk` - Node's node_modules walk done by hand, the - * SDK-checkout self case, and the `bin/lousho.js`/`built` gates. - */ -import * as fs from 'node:fs'; -import * as os from 'node:os'; -import * as path from 'node:path'; -import { afterEach, beforeEach, describe, expect, it } from 'vitest'; -import { findWorkspaceSdk } from '../host/workspaceSdk'; - -let dir: string; - -beforeEach(() => { - dir = fs.mkdtempSync(path.join(os.tmpdir(), 'lou-w11b-sdk-')); -}); - -afterEach(() => { - fs.rmSync(dir, { recursive: true, force: true }); -}); - -/** Writes a minimal SDK install: manifest + bin + optional dist marker. */ -function installSdk(nodeModulesDir: string, name: string, options: { built?: boolean; bin?: boolean } = {}): string { - const pkgDir = path.join(nodeModulesDir, ...name.split('/')); - fs.mkdirSync(path.join(pkgDir, 'bin'), { recursive: true }); - fs.writeFileSync(path.join(pkgDir, 'package.json'), JSON.stringify({ name, version: '1.2.3' })); - if (options.bin !== false) fs.writeFileSync(path.join(pkgDir, 'bin', 'lousho.js'), '// bin\n'); - if (options.built) { - fs.mkdirSync(path.join(pkgDir, 'dist', 'cli'), { recursive: true }); - fs.writeFileSync(path.join(pkgDir, 'dist', 'cli', 'bridge.mjs'), '// bridge\n'); - } - return pkgDir; -} - -describe('findWorkspaceSdk', () => { - it('returns undefined for a folder with no SDK anywhere up the tree', () => { - // os.tmpdir() has no node_modules/@lousho or /lousho above it. - expect(findWorkspaceSdk(dir)).toBeUndefined(); - }); - - it('returns undefined for a nonexistent root', () => { - expect(findWorkspaceSdk(path.join(dir, 'nope'))).toBeUndefined(); - }); - - it('finds @lousho/build-ai-agent in the workspace root node_modules', () => { - const pkgDir = installSdk(path.join(dir, 'node_modules'), '@lousho/build-ai-agent', { built: true }); - const sdk = findWorkspaceSdk(dir); - expect(sdk).toMatchObject({ - version: '1.2.3', - packageDir: fs.realpathSync(pkgDir), - binPath: path.join(fs.realpathSync(pkgDir), 'bin', 'lousho.js'), - source: 'node_modules', - built: true, - }); - }); - - it('finds lousho (the name the package ships under today) the same way', () => { - installSdk(path.join(dir, 'node_modules'), 'lousho'); - expect(findWorkspaceSdk(dir)?.source).toBe('node_modules'); - }); - - it('walks up to a parent folder node_modules (nearest hit wins)', () => { - installSdk(path.join(dir, 'node_modules'), 'lousho', { built: true }); - const nested = path.join(dir, 'a', 'b'); - fs.mkdirSync(nested, { recursive: true }); - // A nearer install shadows the parent's. - installSdk(path.join(dir, 'a', 'node_modules'), 'lousho'); - const sdk = findWorkspaceSdk(nested); - expect(sdk?.packageDir).toBe(fs.realpathSync(path.join(dir, 'a', 'node_modules', 'lousho'))); - }); - - it('prefers the scoped name over lousho at the same level', () => { - installSdk(path.join(dir, 'node_modules'), 'lousho'); - installSdk(path.join(dir, 'node_modules'), '@lousho/build-ai-agent'); - const sdk = findWorkspaceSdk(dir); - expect(sdk?.packageDir).toContain(`${path.sep}@lousho${path.sep}build-ai-agent`); - }); - - it('classifies the SDK checkout itself as self, built only with dist/cli/bridge.mjs', () => { - fs.writeFileSync(path.join(dir, 'package.json'), JSON.stringify({ name: 'lousho', version: '9.9.9' })); - fs.mkdirSync(path.join(dir, 'bin'), { recursive: true }); - fs.writeFileSync(path.join(dir, 'bin', 'lousho.js'), '// bin\n'); - const unbuilt = findWorkspaceSdk(dir); - expect(unbuilt).toMatchObject({ source: 'self', version: '9.9.9', built: false }); - fs.mkdirSync(path.join(dir, 'dist', 'cli'), { recursive: true }); - fs.writeFileSync(path.join(dir, 'dist', 'cli', 'bridge.mjs'), '// bridge\n'); - expect(findWorkspaceSdk(dir)?.built).toBe(true); - }); - - it('returns undefined when bin/lousho.js is missing', () => { - installSdk(path.join(dir, 'node_modules'), 'lousho', { bin: false }); - expect(findWorkspaceSdk(dir)).toBeUndefined(); - }); - - it('returns undefined when the manifest name is not the SDK', () => { - const pkgDir = path.join(dir, 'node_modules', 'lousho'); - fs.mkdirSync(path.join(pkgDir, 'bin'), { recursive: true }); - fs.writeFileSync(path.join(pkgDir, 'package.json'), JSON.stringify({ name: 'not-lousho', version: '1.0.0' })); - fs.writeFileSync(path.join(pkgDir, 'bin', 'lousho.js'), '// bin\n'); - expect(findWorkspaceSdk(dir)).toBeUndefined(); - }); -}); +/** + * W11b: `findWorkspaceSdk` - Node's node_modules walk done by hand, the + * SDK-checkout self case, and the `bin/lousho.js`/`built` gates. + */ +import * as fs from 'node:fs'; +import * as os from 'node:os'; +import * as path from 'node:path'; +import { afterEach, beforeEach, describe, expect, it } from 'vitest'; +import { findWorkspaceSdk } from '../host/workspaceSdk'; + +let dir: string; + +beforeEach(() => { + dir = fs.mkdtempSync(path.join(os.tmpdir(), 'lou-w11b-sdk-')); +}); + +afterEach(() => { + fs.rmSync(dir, { recursive: true, force: true }); +}); + +/** Writes a minimal SDK install: manifest + bin + optional dist marker. */ +function installSdk(nodeModulesDir: string, name: string, options: { built?: boolean; bin?: boolean } = {}): string { + const pkgDir = path.join(nodeModulesDir, ...name.split('/')); + fs.mkdirSync(path.join(pkgDir, 'bin'), { recursive: true }); + fs.writeFileSync(path.join(pkgDir, 'package.json'), JSON.stringify({ name, version: '1.2.3' })); + if (options.bin !== false) fs.writeFileSync(path.join(pkgDir, 'bin', 'lousho.js'), '// bin\n'); + if (options.built) { + fs.mkdirSync(path.join(pkgDir, 'dist', 'cli'), { recursive: true }); + fs.writeFileSync(path.join(pkgDir, 'dist', 'cli', 'bridge.mjs'), '// bridge\n'); + } + return pkgDir; +} + +describe('findWorkspaceSdk', () => { + it('returns undefined for a folder with no SDK anywhere up the tree', () => { + // os.tmpdir() has no node_modules/@linuxdevil or /lousho above it. + expect(findWorkspaceSdk(dir)).toBeUndefined(); + }); + + it('returns undefined for a nonexistent root', () => { + expect(findWorkspaceSdk(path.join(dir, 'nope'))).toBeUndefined(); + }); + + it('finds @linuxdevil/lousho in the workspace root node_modules', () => { + const pkgDir = installSdk(path.join(dir, 'node_modules'), '@linuxdevil/lousho', { built: true }); + const sdk = findWorkspaceSdk(dir); + expect(sdk).toMatchObject({ + version: '1.2.3', + packageDir: fs.realpathSync(pkgDir), + binPath: path.join(fs.realpathSync(pkgDir), 'bin', 'lousho.js'), + source: 'node_modules', + built: true, + }); + }); + + it('finds lousho (the name the package ships under today) the same way', () => { + installSdk(path.join(dir, 'node_modules'), 'lousho'); + expect(findWorkspaceSdk(dir)?.source).toBe('node_modules'); + }); + + it('walks up to a parent folder node_modules (nearest hit wins)', () => { + installSdk(path.join(dir, 'node_modules'), 'lousho', { built: true }); + const nested = path.join(dir, 'a', 'b'); + fs.mkdirSync(nested, { recursive: true }); + // A nearer install shadows the parent's. + installSdk(path.join(dir, 'a', 'node_modules'), 'lousho'); + const sdk = findWorkspaceSdk(nested); + expect(sdk?.packageDir).toBe(fs.realpathSync(path.join(dir, 'a', 'node_modules', 'lousho'))); + }); + + it('prefers the scoped name over lousho at the same level', () => { + installSdk(path.join(dir, 'node_modules'), 'lousho'); + installSdk(path.join(dir, 'node_modules'), '@linuxdevil/lousho'); + const sdk = findWorkspaceSdk(dir); + expect(sdk?.packageDir).toContain(`${path.sep}@linuxdevil${path.sep}lousho`); + }); + + it('classifies the SDK checkout itself as self, built only with dist/cli/bridge.mjs', () => { + fs.writeFileSync(path.join(dir, 'package.json'), JSON.stringify({ name: 'lousho', version: '9.9.9' })); + fs.mkdirSync(path.join(dir, 'bin'), { recursive: true }); + fs.writeFileSync(path.join(dir, 'bin', 'lousho.js'), '// bin\n'); + const unbuilt = findWorkspaceSdk(dir); + expect(unbuilt).toMatchObject({ source: 'self', version: '9.9.9', built: false }); + fs.mkdirSync(path.join(dir, 'dist', 'cli'), { recursive: true }); + fs.writeFileSync(path.join(dir, 'dist', 'cli', 'bridge.mjs'), '// bridge\n'); + expect(findWorkspaceSdk(dir)?.built).toBe(true); + }); + + it('returns undefined when bin/lousho.js is missing', () => { + installSdk(path.join(dir, 'node_modules'), 'lousho', { bin: false }); + expect(findWorkspaceSdk(dir)).toBeUndefined(); + }); + + it('returns undefined when the manifest name is not the SDK', () => { + const pkgDir = path.join(dir, 'node_modules', 'lousho'); + fs.mkdirSync(path.join(pkgDir, 'bin'), { recursive: true }); + fs.writeFileSync(path.join(pkgDir, 'package.json'), JSON.stringify({ name: 'not-lousho', version: '1.0.0' })); + fs.writeFileSync(path.join(pkgDir, 'bin', 'lousho.js'), '// bin\n'); + expect(findWorkspaceSdk(dir)).toBeUndefined(); + }); +}); diff --git a/apps/agent-forge/server/__tests__/workspaceSeam.test.ts b/apps/agent-forge/server/__tests__/workspaceSeam.test.ts index cbb589002..08ddcf616 100644 --- a/apps/agent-forge/server/__tests__/workspaceSeam.test.ts +++ b/apps/agent-forge/server/__tests__/workspaceSeam.test.ts @@ -3,8 +3,8 @@ import * as fs from 'node:fs'; import * as os from 'node:os'; import * as path from 'node:path'; import request from 'supertest'; -import type { AgentSpec } from 'lousho'; -import { TriggerRegistry } from 'lousho/triggers'; +import type { AgentSpec } from '@linuxdevil/lousho'; +import { TriggerRegistry } from '@linuxdevil/lousho/triggers'; import { createApp } from '../app'; import { RunManager } from '../runRegistry'; import { newTestHost } from './testHost'; diff --git a/apps/agent-forge/server/abortableProvider.test.ts b/apps/agent-forge/server/abortableProvider.test.ts index c49d96463..1dae503ee 100644 --- a/apps/agent-forge/server/abortableProvider.test.ts +++ b/apps/agent-forge/server/abortableProvider.test.ts @@ -1,5 +1,5 @@ import { describe, expect, it } from 'vitest'; -import { MockLLMProvider } from 'lousho'; +import { MockLLMProvider } from '@linuxdevil/lousho'; import { RunAbortedError, withAbortSignal } from './abortableProvider'; const request = { model: 'mock-1', messages: [{ role: 'user' as const, content: 'hi' }] }; diff --git a/apps/agent-forge/server/abortableProvider.ts b/apps/agent-forge/server/abortableProvider.ts index 14ae5be26..aa6452149 100644 --- a/apps/agent-forge/server/abortableProvider.ts +++ b/apps/agent-forge/server/abortableProvider.ts @@ -40,7 +40,7 @@ * cancellation; this wrapper's shape (signal checked, forwarded if the * real provider accepts one) is what that wiring would build on. */ -import type { LLMProvider, GenerateOptions, GenerateResult, StreamChunk, StreamResult } from 'lousho'; +import type { LLMProvider, GenerateOptions, GenerateResult, StreamChunk, StreamResult } from '@linuxdevil/lousho'; /** * Named `AbortError` (the fetch/AbortSignal convention) on purpose: the SDK's diff --git a/apps/agent-forge/server/app.ts b/apps/agent-forge/server/app.ts index 94ce5d62b..36017f02c 100644 --- a/apps/agent-forge/server/app.ts +++ b/apps/agent-forge/server/app.ts @@ -86,8 +86,8 @@ import { type AgentSpec, type AgentSpecV2, type ForkPatch, -} from 'lousho'; -import type { connectMcp } from 'lousho/mcp'; +} from '@linuxdevil/lousho'; +import type { connectMcp } from '@linuxdevil/lousho/mcp'; import type { LegacyAgentStore } from '../src/persistence/AgentStore'; import { RunManager, diff --git a/apps/agent-forge/server/approvalStore.ts b/apps/agent-forge/server/approvalStore.ts index 047eb89c3..1a24160f0 100644 --- a/apps/agent-forge/server/approvalStore.ts +++ b/apps/agent-forge/server/approvalStore.ts @@ -8,7 +8,7 @@ */ import * as fs from 'node:fs'; import * as path from 'node:path'; -import type { ApprovalStore, ExecutionSnapshot, PendingApproval, ResolvedApproval } from 'lousho'; +import type { ApprovalStore, ExecutionSnapshot, PendingApproval, ResolvedApproval } from '@linuxdevil/lousho'; export class FileApprovalStore implements ApprovalStore { constructor(private readonly baseDir: string) {} diff --git a/apps/agent-forge/server/author/__tests__/provider.test.ts b/apps/agent-forge/server/author/__tests__/provider.test.ts index 791352eb2..8b3dd1ea1 100644 --- a/apps/agent-forge/server/author/__tests__/provider.test.ts +++ b/apps/agent-forge/server/author/__tests__/provider.test.ts @@ -2,7 +2,7 @@ import { describe, it, expect, beforeEach, afterEach, vi, type MockInstance } fr import * as fs from 'node:fs'; import * as os from 'node:os'; import * as path from 'node:path'; -import { LLMProviderRegistry, type LLMProvider } from 'lousho'; +import { LLMProviderRegistry, type LLMProvider } from '@linuxdevil/lousho'; import { SecretsStore } from '../../secretsStore'; import { resolveAuthorProvider } from '../provider'; diff --git a/apps/agent-forge/server/author/__tests__/requirements.test.ts b/apps/agent-forge/server/author/__tests__/requirements.test.ts index bb46e50c7..181ed9c31 100644 --- a/apps/agent-forge/server/author/__tests__/requirements.test.ts +++ b/apps/agent-forge/server/author/__tests__/requirements.test.ts @@ -8,7 +8,7 @@ import { describe, it, expect, afterEach, vi } from 'vitest'; import * as fs from 'node:fs'; import * as os from 'node:os'; import * as path from 'node:path'; -import type { AgentSpecV2 } from 'lousho'; +import type { AgentSpecV2 } from '@linuxdevil/lousho'; import { computeRequirements, requirementDeps, type RequirementDeps } from '../requirements'; import type { Requirement } from '../../../shared/authorWire'; import type { SecretsStore } from '../../secretsStore'; diff --git a/apps/agent-forge/server/author/__tests__/saveFlow.test.ts b/apps/agent-forge/server/author/__tests__/saveFlow.test.ts index 03a167c59..eb04c7531 100644 --- a/apps/agent-forge/server/author/__tests__/saveFlow.test.ts +++ b/apps/agent-forge/server/author/__tests__/saveFlow.test.ts @@ -2,8 +2,8 @@ import { describe, it, expect, beforeEach, afterEach } from 'vitest'; import * as fs from 'node:fs'; import * as os from 'node:os'; import * as path from 'node:path'; -import { mockModel, type MockModel } from 'lousho/testing'; -import type { AgentSpecV2 } from 'lousho'; +import { mockModel, type MockModel } from '@linuxdevil/lousho/testing'; +import type { AgentSpecV2 } from '@linuxdevil/lousho'; import { AuthorSessionStore } from '../sessionStore'; import { SettingsStore } from '../../settingsStore'; import { SecretsStore } from '../../secretsStore'; diff --git a/apps/agent-forge/server/author/__tests__/turnContext.test.ts b/apps/agent-forge/server/author/__tests__/turnContext.test.ts index b78156454..31720422f 100644 --- a/apps/agent-forge/server/author/__tests__/turnContext.test.ts +++ b/apps/agent-forge/server/author/__tests__/turnContext.test.ts @@ -1,6 +1,6 @@ import { describe, it, expect } from 'vitest'; import { buildTurnContext, specSummary, TURN_CONTEXT_HEADER, type TurnContextInput } from '../turnContext'; -import type { AgentSpecV2 } from 'lousho'; +import type { AgentSpecV2 } from '@linuxdevil/lousho'; /** * buildTurnContext: the per-turn block the runner prepends to the user diff --git a/apps/agent-forge/server/author/__tests__/turnRunner.test.ts b/apps/agent-forge/server/author/__tests__/turnRunner.test.ts index 91d3dad80..6f630674b 100644 --- a/apps/agent-forge/server/author/__tests__/turnRunner.test.ts +++ b/apps/agent-forge/server/author/__tests__/turnRunner.test.ts @@ -2,8 +2,8 @@ import { describe, it, expect, beforeEach, afterEach } from 'vitest'; import * as fs from 'node:fs'; import * as os from 'node:os'; import * as path from 'node:path'; -import { mockModel, type MockModel } from 'lousho/testing'; -import type { NamedToolDescriptor, ToolDescriptor } from 'lousho'; +import { mockModel, type MockModel } from '@linuxdevil/lousho/testing'; +import type { NamedToolDescriptor, ToolDescriptor } from '@linuxdevil/lousho'; import { AuthorSessionStore } from '../sessionStore'; import { SettingsStore } from '../../settingsStore'; import { SecretsStore } from '../../secretsStore'; diff --git a/apps/agent-forge/server/author/draft/__tests__/toSpec.test.ts b/apps/agent-forge/server/author/draft/__tests__/toSpec.test.ts index eb76081a5..fc3e14fbe 100644 --- a/apps/agent-forge/server/author/draft/__tests__/toSpec.test.ts +++ b/apps/agent-forge/server/author/draft/__tests__/toSpec.test.ts @@ -1,5 +1,5 @@ import { describe, expect, it } from 'vitest'; -import type { AgentSpecV2 } from 'lousho'; +import type { AgentSpecV2 } from '@linuxdevil/lousho'; import { isValidAgentId } from '../../../../shared/agentId'; import type { AuthorDraft, AuthorPatch } from '../schema'; import { applyAuthorPatch, deriveAgentId, draftInvalidFailure, draftToSpec } from '../toSpec'; diff --git a/apps/agent-forge/server/author/draft/schema.ts b/apps/agent-forge/server/author/draft/schema.ts index 1b73514fa..b24c577d4 100644 --- a/apps/agent-forge/server/author/draft/schema.ts +++ b/apps/agent-forge/server/author/draft/schema.ts @@ -13,8 +13,8 @@ * JSON Schema surfaces to the model. */ import { z } from 'zod'; -import { agentSpecV2Schema } from 'lousho'; -import type { AgentSpecV2 } from 'lousho'; +import { agentSpecV2Schema } from '@linuxdevil/lousho'; +import type { AgentSpecV2 } from '@linuxdevil/lousho'; /** The spec keys the Author never sets: `version` is forced to 2, `evals` and `flow` are not in v1 of the Author. */ export const DRAFT_OMITTED_KEYS = ['version', 'evals', 'flow'] as const; diff --git a/apps/agent-forge/server/author/draft/toSpec.ts b/apps/agent-forge/server/author/draft/toSpec.ts index bae53fa62..52310862c 100644 --- a/apps/agent-forge/server/author/draft/toSpec.ts +++ b/apps/agent-forge/server/author/draft/toSpec.ts @@ -10,8 +10,8 @@ * Studio could not store. Catalog checks (is the model real, is the tool * known) and diffs are the propose tools' job, not this layer's. */ -import { agentSpecV2Schema } from 'lousho'; -import type { AgentSpecV2 } from 'lousho'; +import { agentSpecV2Schema } from '@linuxdevil/lousho'; +import type { AgentSpecV2 } from '@linuxdevil/lousho'; import type { AuthorToolFailure } from '../../../shared/authorWire'; import { toolFailure } from '../tools/failure'; import type { AuthorDraft, AuthorPatch } from './schema'; diff --git a/apps/agent-forge/server/author/modelMessages.ts b/apps/agent-forge/server/author/modelMessages.ts index 4ed08e054..2372ad5be 100644 --- a/apps/agent-forge/server/author/modelMessages.ts +++ b/apps/agent-forge/server/author/modelMessages.ts @@ -11,7 +11,7 @@ * messages (button saves, secret cards) arrive as user text starting with * `[UI event]` so the model never mistakes them for its own tool calls. */ -import type { Message, ToolCall } from 'lousho'; +import type { Message, ToolCall } from '@linuxdevil/lousho'; import type { AuthorAssistantMessage, AuthorMessage, diff --git a/apps/agent-forge/server/author/proposal/__tests__/catalogChecks.test.ts b/apps/agent-forge/server/author/proposal/__tests__/catalogChecks.test.ts index 4d5379179..7ff31e677 100644 --- a/apps/agent-forge/server/author/proposal/__tests__/catalogChecks.test.ts +++ b/apps/agent-forge/server/author/proposal/__tests__/catalogChecks.test.ts @@ -1,5 +1,5 @@ import { describe, expect, it } from 'vitest'; -import type { AgentSpecV2 } from 'lousho'; +import type { AgentSpecV2 } from '@linuxdevil/lousho'; import type { AuthorToolDeps, CatalogService } from '../../tools/index'; import type { AuthorTurn } from '../../turnRunner'; import type { WorkspaceContext } from '../../../workspace/context'; diff --git a/apps/agent-forge/server/author/proposal/__tests__/gate.test.ts b/apps/agent-forge/server/author/proposal/__tests__/gate.test.ts index ec4ad3368..41dfe677c 100644 --- a/apps/agent-forge/server/author/proposal/__tests__/gate.test.ts +++ b/apps/agent-forge/server/author/proposal/__tests__/gate.test.ts @@ -1,5 +1,5 @@ import { describe, expect, it } from 'vitest'; -import type { AgentSpecV2 } from 'lousho'; +import type { AgentSpecV2 } from '@linuxdevil/lousho'; import type { AuthorAssistantMessage, AuthorMessage, diff --git a/apps/agent-forge/server/author/proposal/__tests__/save.test.ts b/apps/agent-forge/server/author/proposal/__tests__/save.test.ts index ee705354b..735df0bbc 100644 --- a/apps/agent-forge/server/author/proposal/__tests__/save.test.ts +++ b/apps/agent-forge/server/author/proposal/__tests__/save.test.ts @@ -2,7 +2,7 @@ import { describe, it, expect, beforeEach, afterEach } from 'vitest'; import { mkdtempSync, rmSync } from 'node:fs'; import { tmpdir } from 'node:os'; import * as path from 'node:path'; -import { agentSpecV2Schema, type AgentSpecV2 } from 'lousho'; +import { agentSpecV2Schema, type AgentSpecV2 } from '@linuxdevil/lousho'; import type { AuthorToolFailure, ProposalOutput } from '../../../../shared/authorWire'; import type { StudioEvent } from '../../../../shared/wireTypes'; import { AgentRepo, defaultMeta } from '../../../storage/agentRepo'; diff --git a/apps/agent-forge/server/author/proposal/catalogChecks.ts b/apps/agent-forge/server/author/proposal/catalogChecks.ts index b17e537e5..662e94e61 100644 --- a/apps/agent-forge/server/author/proposal/catalogChecks.ts +++ b/apps/agent-forge/server/author/proposal/catalogChecks.ts @@ -15,7 +15,7 @@ * `turn.state['catalogFacts']`): the model may propose several drafts in * a turn and the catalogs cannot change mid-turn. */ -import type { AgentSpecV2 } from 'lousho'; +import type { AgentSpecV2 } from '@linuxdevil/lousho'; import type { AuthorFailureCode, AuthorToolFailure } from '../../../shared/authorWire'; import type { AuthorTurn } from '../turnRunner'; import { toolFailure } from '../tools/failure'; diff --git a/apps/agent-forge/server/author/proposal/fold.ts b/apps/agent-forge/server/author/proposal/fold.ts index dceda2df5..52f7ab816 100644 --- a/apps/agent-forge/server/author/proposal/fold.ts +++ b/apps/agent-forge/server/author/proposal/fold.ts @@ -12,7 +12,7 @@ * returned here but ignored by the caller - folding is keyed on the spec * the card was built against, not on the card alone. */ -import type { AgentSpecV2 } from 'lousho'; +import type { AgentSpecV2 } from '@linuxdevil/lousho'; import type { ProposalOutput } from '../../../shared/authorWire'; import { SPEC_FIELD_ORDER } from '../../../shared/specFields'; import { stableStringify } from '../../../shared/stableJson'; diff --git a/apps/agent-forge/server/author/proposal/review.ts b/apps/agent-forge/server/author/proposal/review.ts index a6338a5c6..742840cc8 100644 --- a/apps/agent-forge/server/author/proposal/review.ts +++ b/apps/agent-forge/server/author/proposal/review.ts @@ -9,7 +9,7 @@ * `AuthorToolFailure` the tool hands back to the model), never a throw - * the review bouncing a draft is data, not an error. */ -import type { AgentSpecV2 } from 'lousho'; +import type { AgentSpecV2 } from '@linuxdevil/lousho'; import type { AuthorToolFailure, Requirement, SpecChange } from '../../../shared/authorWire'; import type { AuthorTurn } from '../turnRunner'; import type { ProposalIdentity } from './turnState'; diff --git a/apps/agent-forge/server/author/proposal/save.ts b/apps/agent-forge/server/author/proposal/save.ts index a48681f28..0681b449c 100644 --- a/apps/agent-forge/server/author/proposal/save.ts +++ b/apps/agent-forge/server/author/proposal/save.ts @@ -12,7 +12,7 @@ * (gate.ts) decides WHICH card may save - this module only performs the * write itself once a caller has resolved one. */ -import type { AgentSpecV2 } from 'lousho'; +import type { AgentSpecV2 } from '@linuxdevil/lousho'; import type { AuthorToolFailure, ProposalOutput, diff --git a/apps/agent-forge/server/author/provider.ts b/apps/agent-forge/server/author/provider.ts index 0b40ec014..139e42c38 100644 --- a/apps/agent-forge/server/author/provider.ts +++ b/apps/agent-forge/server/author/provider.ts @@ -6,7 +6,7 @@ * hatch: with no usable key the Author shows a setup card instead of * pretending to answer (PLAN 16 decision 4). */ -import { LLMProviderRegistry, type LLMProvider } from 'lousho'; +import { LLMProviderRegistry, type LLMProvider } from '@linuxdevil/lousho'; import { PROVIDER_KEY_ENV, isSecretProvider, type SecretsStore } from '../secretsStore'; /** The outcome of {@link resolveAuthorProvider}: a ready provider, or why the Author cannot run. */ diff --git a/apps/agent-forge/server/author/requirements.ts b/apps/agent-forge/server/author/requirements.ts index 07f09fdf3..933c18547 100644 --- a/apps/agent-forge/server/author/requirements.ts +++ b/apps/agent-forge/server/author/requirements.ts @@ -19,7 +19,7 @@ */ import * as fs from 'node:fs'; import * as path from 'node:path'; -import { listBuiltinTools, listModels, type AgentSpecV2 } from 'lousho'; +import { listBuiltinTools, listModels, type AgentSpecV2 } from '@linuxdevil/lousho'; import type { Requirement } from '../../shared/authorWire'; import { isSecretProvider, PROVIDER_KEY_ENV, type SecretProvider, type SecretsStore } from '../secretsStore'; diff --git a/apps/agent-forge/server/author/review/__tests__/reviewDraft.test.ts b/apps/agent-forge/server/author/review/__tests__/reviewDraft.test.ts index c5e45bbd7..caa3cd5de 100644 --- a/apps/agent-forge/server/author/review/__tests__/reviewDraft.test.ts +++ b/apps/agent-forge/server/author/review/__tests__/reviewDraft.test.ts @@ -2,8 +2,8 @@ import { mkdtempSync } from 'node:fs'; import { tmpdir } from 'node:os'; import * as path from 'node:path'; import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; -import { mockModel, type MockModel } from 'lousho/testing'; -import type { AgentSpecV2 } from 'lousho'; +import { mockModel, type MockModel } from '@linuxdevil/lousho/testing'; +import type { AgentSpecV2 } from '@linuxdevil/lousho'; import { DRAFT_REVIEW_DIMENSIONS, isDraftReviewFailure, type AuthorToolFailure } from '../../../../shared/authorWire'; import { SettingsStore } from '../../../settingsStore'; import { SecretsStore } from '../../../secretsStore'; diff --git a/apps/agent-forge/server/author/review/__tests__/state.test.ts b/apps/agent-forge/server/author/review/__tests__/state.test.ts index 820528ab7..f4bdba405 100644 --- a/apps/agent-forge/server/author/review/__tests__/state.test.ts +++ b/apps/agent-forge/server/author/review/__tests__/state.test.ts @@ -1,5 +1,5 @@ import { describe, expect, it } from 'vitest'; -import type { AgentSpecV2 } from 'lousho'; +import type { AgentSpecV2 } from '@linuxdevil/lousho'; import type { AuthorMessage, Requirement, SpecChange } from '../../../../shared/authorWire'; import type { AuthorTurn } from '../../turnRunner'; import type { ReviewInput } from '../../proposal/review'; diff --git a/apps/agent-forge/server/author/review/reviewDraft.ts b/apps/agent-forge/server/author/review/reviewDraft.ts index 7214e6af8..3e3eec320 100644 --- a/apps/agent-forge/server/author/review/reviewDraft.ts +++ b/apps/agent-forge/server/author/review/reviewDraft.ts @@ -9,7 +9,7 @@ * no steps left to redraft in) fails open: the review never hides a card * it could not judge. */ -import { createAgent, listBuiltinTools, listGuardrails, listTriggerTypes, type LLMProvider } from 'lousho'; +import { createAgent, listBuiltinTools, listGuardrails, listTriggerTypes, type LLMProvider } from '@linuxdevil/lousho'; import { z } from 'zod'; import { DRAFT_REVIEW_DIMENSIONS, diff --git a/apps/agent-forge/server/author/review/state.ts b/apps/agent-forge/server/author/review/state.ts index 23ef692a6..e5a7f1d01 100644 --- a/apps/agent-forge/server/author/review/state.ts +++ b/apps/agent-forge/server/author/review/state.ts @@ -7,7 +7,7 @@ * cannot blow the review prompt up, and the catalog facts the judge * checks membership against. */ -import type { AgentSpecV2 } from 'lousho'; +import type { AgentSpecV2 } from '@linuxdevil/lousho'; import type { AuthorMessage, Requirement, SpecChange } from '../../../shared/authorWire'; import type { CatalogFacts } from '../proposal/catalogChecks'; import type { ReviewInput } from '../proposal/review'; diff --git a/apps/agent-forge/server/author/services.ts b/apps/agent-forge/server/author/services.ts index 98ff85f46..6ff6c77fe 100644 --- a/apps/agent-forge/server/author/services.ts +++ b/apps/agent-forge/server/author/services.ts @@ -12,8 +12,8 @@ * seams (`providerFor`, `reviewDraft`, `mcpConnect`) exist for tests and * the eval's `--mock`/`--no-review` modes. */ -import type { LLMProvider } from 'lousho'; -import type { connectMcp } from 'lousho/mcp'; +import type { LLMProvider } from '@linuxdevil/lousho'; +import type { connectMcp } from '@linuxdevil/lousho/mcp'; import type { AuthorSession } from '../../shared/authorWire'; import type { SettingsStore } from '../settingsStore'; import type { SecretsStore } from '../secretsStore'; diff --git a/apps/agent-forge/server/author/tools/__tests__/condenseTrace.test.ts b/apps/agent-forge/server/author/tools/__tests__/condenseTrace.test.ts index 5f1f7d273..d2092edfe 100644 --- a/apps/agent-forge/server/author/tools/__tests__/condenseTrace.test.ts +++ b/apps/agent-forge/server/author/tools/__tests__/condenseTrace.test.ts @@ -2,8 +2,8 @@ import { mkdtempSync, mkdirSync, readFileSync, rmSync, writeFileSync } from 'nod import { tmpdir } from 'node:os'; import * as path from 'node:path'; import { afterEach, describe, expect, it } from 'vitest'; -import { testToolContext } from 'lousho/testing'; -import type { ToolDescriptor } from 'lousho'; +import { testToolContext } from '@linuxdevil/lousho/testing'; +import type { ToolDescriptor } from '@linuxdevil/lousho'; import { condenseTrace, TRACE_STEP_LIMIT, type GetTraceOutput } from '../condenseTrace'; import { agentTraceDir } from '../../../traceStore'; import { AgentRepo } from '../../../storage/agentRepo'; diff --git a/apps/agent-forge/server/author/tools/__tests__/proposeAgent.test.ts b/apps/agent-forge/server/author/tools/__tests__/proposeAgent.test.ts index c40899f0b..d346afdd1 100644 --- a/apps/agent-forge/server/author/tools/__tests__/proposeAgent.test.ts +++ b/apps/agent-forge/server/author/tools/__tests__/proposeAgent.test.ts @@ -2,8 +2,8 @@ import { mkdirSync, mkdtempSync, readdirSync } from 'node:fs'; import { tmpdir } from 'node:os'; import * as path from 'node:path'; import { describe, expect, it } from 'vitest'; -import { testToolContext } from 'lousho/testing'; -import type { AgentSpecV2 } from 'lousho'; +import { testToolContext } from '@linuxdevil/lousho/testing'; +import type { AgentSpecV2 } from '@linuxdevil/lousho'; import type { AuthorToolDeps, CatalogService } from '../index'; import type { AuthorTurn } from '../../turnRunner'; import type { WorkspaceContext } from '../../../workspace/context'; diff --git a/apps/agent-forge/server/author/tools/__tests__/proposeAgentUpdate.test.ts b/apps/agent-forge/server/author/tools/__tests__/proposeAgentUpdate.test.ts index 58a7f8e13..aec2584c7 100644 --- a/apps/agent-forge/server/author/tools/__tests__/proposeAgentUpdate.test.ts +++ b/apps/agent-forge/server/author/tools/__tests__/proposeAgentUpdate.test.ts @@ -2,8 +2,8 @@ import { mkdtempSync, readdirSync, readFileSync } from 'node:fs'; import { tmpdir } from 'node:os'; import * as path from 'node:path'; import { describe, expect, it } from 'vitest'; -import { testToolContext } from 'lousho/testing'; -import type { AgentSpecV2 } from 'lousho'; +import { testToolContext } from '@linuxdevil/lousho/testing'; +import type { AgentSpecV2 } from '@linuxdevil/lousho'; import type { AuthorToolDeps, CatalogService } from '../index'; import type { AuthorTurn } from '../../turnRunner'; import type { WorkspaceContext } from '../../../workspace/context'; diff --git a/apps/agent-forge/server/author/tools/__tests__/readTools.test.ts b/apps/agent-forge/server/author/tools/__tests__/readTools.test.ts index f328db04c..3ca6c1ef2 100644 --- a/apps/agent-forge/server/author/tools/__tests__/readTools.test.ts +++ b/apps/agent-forge/server/author/tools/__tests__/readTools.test.ts @@ -1,5 +1,5 @@ import { describe, it, expect } from 'vitest'; -import type { AgentSpecV2 } from 'lousho'; +import type { AgentSpecV2 } from '@linuxdevil/lousho'; import type { AgentRepo } from '../../../storage/agentRepo'; import type { VersionStore } from '../../../storage/versionStore'; import type { WorkspaceContext } from '../../../workspace/context'; diff --git a/apps/agent-forge/server/author/tools/__tests__/requestSecret.test.ts b/apps/agent-forge/server/author/tools/__tests__/requestSecret.test.ts index 74cb27c2f..ef6af340d 100644 --- a/apps/agent-forge/server/author/tools/__tests__/requestSecret.test.ts +++ b/apps/agent-forge/server/author/tools/__tests__/requestSecret.test.ts @@ -2,7 +2,7 @@ import { describe, it, expect, beforeEach, afterEach } from 'vitest'; import * as fs from 'node:fs'; import * as os from 'node:os'; import * as path from 'node:path'; -import { testToolContext } from 'lousho/testing'; +import { testToolContext } from '@linuxdevil/lousho/testing'; import { AUTHOR_TOOL_FACTORIES, buildAuthorTools, type AuthorToolDeps } from '../index'; import type { AuthorTurn } from '../../turnRunner'; import type { AuthorToolFailure, RequestSecretOutput } from '../../../../shared/authorWire'; diff --git a/apps/agent-forge/server/author/tools/__tests__/runDraft.test.ts b/apps/agent-forge/server/author/tools/__tests__/runDraft.test.ts index 769ecf8bb..f8aee0117 100644 --- a/apps/agent-forge/server/author/tools/__tests__/runDraft.test.ts +++ b/apps/agent-forge/server/author/tools/__tests__/runDraft.test.ts @@ -3,8 +3,8 @@ import { mkdtempSync, rmSync } from 'node:fs'; import { tmpdir } from 'node:os'; import * as path from 'node:path'; import { afterEach, describe, expect, it, vi } from 'vitest'; -import { testToolContext } from 'lousho/testing'; -import { ConfigurationError, type AgentSpecV2, type ToolDescriptor } from 'lousho'; +import { testToolContext } from '@linuxdevil/lousho/testing'; +import { ConfigurationError, type AgentSpecV2, type ToolDescriptor } from '@linuxdevil/lousho'; import { AlreadyRunningError, RunManager } from '../../../runRegistry'; import { readAgentTrace } from '../../../traceStore'; import { AgentRepo } from '../../../storage/agentRepo'; diff --git a/apps/agent-forge/server/author/tools/__tests__/stateTools.test.ts b/apps/agent-forge/server/author/tools/__tests__/stateTools.test.ts index 6818feb7d..646c8bf0b 100644 --- a/apps/agent-forge/server/author/tools/__tests__/stateTools.test.ts +++ b/apps/agent-forge/server/author/tools/__tests__/stateTools.test.ts @@ -2,8 +2,8 @@ import { mkdtempSync, rmSync } from 'node:fs'; import { tmpdir } from 'node:os'; import * as path from 'node:path'; import { afterEach, describe, expect, it } from 'vitest'; -import { testToolContext } from 'lousho/testing'; -import type { AgentSpecV2, ToolDescriptor } from 'lousho'; +import { testToolContext } from '@linuxdevil/lousho/testing'; +import type { AgentSpecV2, ToolDescriptor } from '@linuxdevil/lousho'; import { buildAuthorTools, type AuthorToolDeps, type CatalogService } from '../index'; import type { AuthorTurn } from '../../turnRunner'; import type { WorkspaceContext } from '../../../workspace/context'; diff --git a/apps/agent-forge/server/author/tools/catalogService.ts b/apps/agent-forge/server/author/tools/catalogService.ts index a97ed5fbf..fea8bc250 100644 --- a/apps/agent-forge/server/author/tools/catalogService.ts +++ b/apps/agent-forge/server/author/tools/catalogService.ts @@ -6,7 +6,7 @@ * (shared with the route's reader so the two never parse it * differently). */ -import { listModels } from 'lousho'; +import { listModels } from '@linuxdevil/lousho'; import type { SecretsStore } from '../../secretsStore'; import { loadStaticCatalog, withStoredKeyAvailability } from '../../routes/catalogRoutes'; import type { CatalogService } from './index'; diff --git a/apps/agent-forge/server/author/tools/getTraceTool.ts b/apps/agent-forge/server/author/tools/getTraceTool.ts index bd360e620..3fb5679a3 100644 --- a/apps/agent-forge/server/author/tools/getTraceTool.ts +++ b/apps/agent-forge/server/author/tools/getTraceTool.ts @@ -8,7 +8,7 @@ * `.lousho/agents//traces`. With `agentId` the lookup is scoped; * without it every saved agent's folder is tried in turn. */ -import { defineTool } from 'lousho'; +import { defineTool } from '@linuxdevil/lousho'; import { z } from 'zod'; import { isValidTraceId, readAgentTrace } from '../../traceStore'; import { isValidAgentId } from '../../../shared/agentId'; diff --git a/apps/agent-forge/server/author/tools/index.ts b/apps/agent-forge/server/author/tools/index.ts index adfa2f079..061dec273 100644 --- a/apps/agent-forge/server/author/tools/index.ts +++ b/apps/agent-forge/server/author/tools/index.ts @@ -14,7 +14,7 @@ * is D17's catalog, `probeMcp` is D11's probe as a function, and * `docsDir` is the bundled SDK docs folder `searchDocs` reads. */ -import type { AgentSpecV2, ToolDescriptor } from 'lousho'; +import type { AgentSpecV2, ToolDescriptor } from '@linuxdevil/lousho'; import type { Requirement } from '../../../shared/authorWire'; import type { CatalogGuardrail, diff --git a/apps/agent-forge/server/author/tools/mcpProbeTool.ts b/apps/agent-forge/server/author/tools/mcpProbeTool.ts index a8e8eb20e..9e6ec10bf 100644 --- a/apps/agent-forge/server/author/tools/mcpProbeTool.ts +++ b/apps/agent-forge/server/author/tools/mcpProbeTool.ts @@ -17,7 +17,7 @@ * deadline, `MCP_PROBE_FAILED` for everything else, and the `UNAVAILABLE` * catch-all for a thrown dep error (guardedToolCall). */ -import { defineTool } from 'lousho'; +import { defineTool } from '@linuxdevil/lousho'; import { z } from 'zod'; import type { McpServerSpecV2 } from '../../../shared/wireTypes'; import { guardedToolCall, toolFailure } from './failure'; diff --git a/apps/agent-forge/server/author/tools/openPageTool.ts b/apps/agent-forge/server/author/tools/openPageTool.ts index 35aa4de69..715a6c7ec 100644 --- a/apps/agent-forge/server/author/tools/openPageTool.ts +++ b/apps/agent-forge/server/author/tools/openPageTool.ts @@ -9,7 +9,7 @@ * the tool's output ends up in a client `history.push`, so anything * else is `INVALID_INPUT`, not a navigation. */ -import { defineTool } from 'lousho'; +import { defineTool } from '@linuxdevil/lousho'; import { z } from 'zod'; import { guardedToolCall, toolFailure } from './failure'; import type { AuthorToolFactory } from './index'; diff --git a/apps/agent-forge/server/author/tools/proposeAgentTool.ts b/apps/agent-forge/server/author/tools/proposeAgentTool.ts index 5a97c27a1..7637f29b0 100644 --- a/apps/agent-forge/server/author/tools/proposeAgentTool.ts +++ b/apps/agent-forge/server/author/tools/proposeAgentTool.ts @@ -13,7 +13,7 @@ * 'create' identity in {@link proposalTurnState} and returned - a failed * step leaves `proposed` untouched. */ -import { defineTool } from 'lousho'; +import { defineTool } from '@linuxdevil/lousho'; import { z } from 'zod'; import type { ProposalOutput } from '../../../shared/authorWire'; import { authorDraftSchema } from '../draft/schema'; diff --git a/apps/agent-forge/server/author/tools/proposeAgentUpdateTool.ts b/apps/agent-forge/server/author/tools/proposeAgentUpdateTool.ts index d717b9f11..d3e5538dd 100644 --- a/apps/agent-forge/server/author/tools/proposeAgentUpdateTool.ts +++ b/apps/agent-forge/server/author/tools/proposeAgentUpdateTool.ts @@ -14,9 +14,9 @@ * and fall back to the saved instructions, then the merged patch runs the * same build / catalog / requirements / review gauntlet as `proposeAgent`. */ -import { defineTool } from 'lousho'; +import { defineTool } from '@linuxdevil/lousho'; import { z } from 'zod'; -import type { AgentSpecV2 } from 'lousho'; +import type { AgentSpecV2 } from '@linuxdevil/lousho'; import type { AuthorToolFailure, ProposalOutput } from '../../../shared/authorWire'; import { diffSpecs } from '../../diff/specChange'; import { specHash } from '../../storage/specHash'; diff --git a/apps/agent-forge/server/author/tools/readTools.ts b/apps/agent-forge/server/author/tools/readTools.ts index 797024ead..2dc5583c7 100644 --- a/apps/agent-forge/server/author/tools/readTools.ts +++ b/apps/agent-forge/server/author/tools/readTools.ts @@ -11,9 +11,9 @@ * `AGENT_NOT_FOUND` of `getCurrentSpec` and the `UNAVAILABLE` catch-all * of `guardedToolCall`. */ -import { defineTool } from 'lousho'; +import { defineTool } from '@linuxdevil/lousho'; import { z } from 'zod'; -import type { AgentSpecV2 } from 'lousho'; +import type { AgentSpecV2 } from '@linuxdevil/lousho'; import type { McpServerSpecV2 } from '../../../shared/wireTypes'; import type { WorkspaceContext } from '../../workspace/context'; import { specHash } from '../../storage/specHash'; diff --git a/apps/agent-forge/server/author/tools/requestSecretTool.ts b/apps/agent-forge/server/author/tools/requestSecretTool.ts index 2818d36ef..83d6eb393 100644 --- a/apps/agent-forge/server/author/tools/requestSecretTool.ts +++ b/apps/agent-forge/server/author/tools/requestSecretTool.ts @@ -12,7 +12,7 @@ * Settings, and letting the Author store a second copy under a named * secret would silently shadow the Settings value. */ -import { defineTool } from 'lousho'; +import { defineTool } from '@linuxdevil/lousho'; import { z } from 'zod'; import type { RequestSecretOutput } from '../../../shared/authorWire'; import { guardedToolCall, toolFailure } from './failure'; diff --git a/apps/agent-forge/server/author/tools/runDraftTool.ts b/apps/agent-forge/server/author/tools/runDraftTool.ts index 679887487..17a6ed7d6 100644 --- a/apps/agent-forge/server/author/tools/runDraftTool.ts +++ b/apps/agent-forge/server/author/tools/runDraftTool.ts @@ -9,7 +9,7 @@ * proposal: `run(agentId, input)` with no spec argument is, by * definition, the saved working copy. */ -import { defineTool, SDKError } from 'lousho'; +import { defineTool, SDKError } from '@linuxdevil/lousho'; import { z } from 'zod'; import { AgentNotFoundError, AlreadyRunningError, type RunManager } from '../../runRegistry'; import { isValidAgentId } from '../../../shared/agentId'; diff --git a/apps/agent-forge/server/author/tools/saveTools.ts b/apps/agent-forge/server/author/tools/saveTools.ts index 21b4febdc..70e65eca8 100644 --- a/apps/agent-forge/server/author/tools/saveTools.ts +++ b/apps/agent-forge/server/author/tools/saveTools.ts @@ -11,7 +11,7 @@ * marks the identity in `proposalTurnState().saved` so a second save in * the same turn refuses `ALREADY_SAVED`. */ -import { defineTool } from 'lousho'; +import { defineTool } from '@linuxdevil/lousho'; import { z } from 'zod'; import type { AuthorToolFailure } from '../../../shared/authorWire'; import { resolveStoredProposal, saveBlockingError } from '../proposal/gate'; diff --git a/apps/agent-forge/server/author/tools/searchDocsTool.ts b/apps/agent-forge/server/author/tools/searchDocsTool.ts index 714e6f009..6627b8497 100644 --- a/apps/agent-forge/server/author/tools/searchDocsTool.ts +++ b/apps/agent-forge/server/author/tools/searchDocsTool.ts @@ -9,7 +9,7 @@ * bundled docs (docsIndex.ts normalizes an unreadable folder to * `undefined`). */ -import { defineTool } from 'lousho'; +import { defineTool } from '@linuxdevil/lousho'; import { z } from 'zod'; import { loadDocsIndex, searchDocs } from './docsIndex'; import { guardedToolCall, toolFailure } from './failure'; diff --git a/apps/agent-forge/server/author/tools/stateTools.ts b/apps/agent-forge/server/author/tools/stateTools.ts index 454af8264..846cd8948 100644 --- a/apps/agent-forge/server/author/tools/stateTools.ts +++ b/apps/agent-forge/server/author/tools/stateTools.ts @@ -16,9 +16,9 @@ * `POST /assistant/sessions/:id/enable` (the SuccessCard's "Enable agent" * button) so the button and the model switch an agent identically. */ -import { defineTool } from 'lousho'; +import { defineTool } from '@linuxdevil/lousho'; import { z } from 'zod'; -import type { AgentSpecV2 } from 'lousho'; +import type { AgentSpecV2 } from '@linuxdevil/lousho'; import type { AuthorToolFailure, Requirement, diff --git a/apps/agent-forge/server/author/turnContext.ts b/apps/agent-forge/server/author/turnContext.ts index 8854e82b4..b421be2a9 100644 --- a/apps/agent-forge/server/author/turnContext.ts +++ b/apps/agent-forge/server/author/turnContext.ts @@ -11,7 +11,7 @@ * appear here: the summary names `mcpServers` keys and `SecretRef` refs, * never their payloads. */ -import type { AgentSpecV2 } from 'lousho'; +import type { AgentSpecV2 } from '@linuxdevil/lousho'; import type { AuthorFocus } from '../../shared/authorWire'; /** The first line of every turn-context block: tells the model the block is app data, not user text. */ diff --git a/apps/agent-forge/server/author/turnRunner.ts b/apps/agent-forge/server/author/turnRunner.ts index f3ebceeb0..98c812193 100644 --- a/apps/agent-forge/server/author/turnRunner.ts +++ b/apps/agent-forge/server/author/turnRunner.ts @@ -20,7 +20,7 @@ import { type AgentEvent, type LLMProvider, type ToolDescriptor, -} from 'lousho'; +} from '@linuxdevil/lousho'; import type { AuthorAssistantMessage, AuthorFocus, diff --git a/apps/agent-forge/server/author/wiring.ts b/apps/agent-forge/server/author/wiring.ts index 0f1fc6bcc..af71984f1 100644 --- a/apps/agent-forge/server/author/wiring.ts +++ b/apps/agent-forge/server/author/wiring.ts @@ -10,8 +10,8 @@ * second runner would have its own turn slots and the one-running-turn- * per-session rule (and the DELETE 409) would stop holding. */ -import { listBuiltinTools, listGuardrails, listModels, listTriggerTypes, type LLMProvider } from 'lousho'; -import type { connectMcp } from 'lousho/mcp'; +import { listBuiltinTools, listGuardrails, listModels, listTriggerTypes, type LLMProvider } from '@linuxdevil/lousho'; +import type { connectMcp } from '@linuxdevil/lousho/mcp'; import type { SettingsStore } from '../settingsStore'; import type { SecretsStore } from '../secretsStore'; import type { TemplateCatalog } from '../templates/templateCatalog'; diff --git a/apps/agent-forge/server/autoApproval.ts b/apps/agent-forge/server/autoApproval.ts index 11f213557..f2d031337 100644 --- a/apps/agent-forge/server/autoApproval.ts +++ b/apps/agent-forge/server/autoApproval.ts @@ -27,7 +27,7 @@ import { type GenerateOptions, type LLMProvider, type ProviderUsage, -} from 'lousho'; +} from '@linuxdevil/lousho'; import type { LogEntry } from '../shared/wireTypes'; import { resolveProviderForModel } from './buildAgent'; import type { PassRecord } from './passes/passRunner'; diff --git a/apps/agent-forge/server/buildAgent.test.ts b/apps/agent-forge/server/buildAgent.test.ts index bf88bd9d7..a19757dee 100644 --- a/apps/agent-forge/server/buildAgent.test.ts +++ b/apps/agent-forge/server/buildAgent.test.ts @@ -2,7 +2,7 @@ import { describe, it, expect, beforeEach, afterEach } from 'vitest'; import * as fs from 'node:fs'; import * as path from 'node:path'; import * as os from 'node:os'; -import { ConfigurationError, type AgentSpec } from 'lousho'; +import { ConfigurationError, type AgentSpec } from '@linuxdevil/lousho'; import { buildAgentFromSpec, resolveProviderForModel } from './buildAgent'; import { SecretsStore } from './secretsStore'; diff --git a/apps/agent-forge/server/buildAgent.ts b/apps/agent-forge/server/buildAgent.ts index 3a4914faa..472ed91c3 100644 --- a/apps/agent-forge/server/buildAgent.ts +++ b/apps/agent-forge/server/buildAgent.ts @@ -14,7 +14,7 @@ * all public SDK exports) itself, rather than reaching past `specToAgent()` * into SDK internals. */ -import { AgentBuilder, ToolRegistry } from 'lousho/executor'; +import { AgentBuilder, ToolRegistry } from '@linuxdevil/lousho/executor'; import { ConfigurationError, NoopSandbox, @@ -28,8 +28,8 @@ import { type LLMProvider, type SandboxAdapter, type ToolDescriptor, -} from 'lousho'; -import type { AgentFlow } from 'lousho/flows'; +} from '@linuxdevil/lousho'; +import type { AgentFlow } from '@linuxdevil/lousho/flows'; import { compileHooksFromSpecPolicy } from './compileHooks'; import { PROVIDER_KEY_ENV, isSecretProvider, type SecretsStore } from './secretsStore'; diff --git a/apps/agent-forge/server/buildAgentV2.ts b/apps/agent-forge/server/buildAgentV2.ts index d507b0d54..f824fb22e 100644 --- a/apps/agent-forge/server/buildAgentV2.ts +++ b/apps/agent-forge/server/buildAgentV2.ts @@ -45,8 +45,8 @@ import { type SpecHost, type ToolDescriptor, type TraceExporter, -} from 'lousho'; -import type { AgentFlow } from 'lousho/flows'; +} from '@linuxdevil/lousho'; +import type { AgentFlow } from '@linuxdevil/lousho/flows'; import { studioAutoApprover } from './autoApproval'; import { asAgentFlow, LOCAL_TOOLS, resolveProviderForModel } from './buildAgent'; import type { FileApprovalStore } from './approvalStore'; diff --git a/apps/agent-forge/server/chatReconcile.ts b/apps/agent-forge/server/chatReconcile.ts index e8f90ac3b..5b3fdd2ba 100644 --- a/apps/agent-forge/server/chatReconcile.ts +++ b/apps/agent-forge/server/chatReconcile.ts @@ -24,7 +24,7 @@ * fresh ones for genuinely new messages. */ import { randomUUID } from 'node:crypto'; -import { textOf, type Message } from 'lousho'; +import { textOf, type Message } from '@linuxdevil/lousho'; import type { ChatMessage, SteerInfo } from '../shared/wireTypes'; /** The transcript keeps text only (content parts are flattened with `textOf()`). */ diff --git a/apps/agent-forge/server/chatTriggerAdapter.ts b/apps/agent-forge/server/chatTriggerAdapter.ts index c5a9cc368..22d055f38 100644 --- a/apps/agent-forge/server/chatTriggerAdapter.ts +++ b/apps/agent-forge/server/chatTriggerAdapter.ts @@ -4,7 +4,7 @@ * Wraps Agent Forge's existing chat transport - `RunManager.sendMessage()` * (see runRegistry.ts's doc comment on that method for the full * continuation semantics) - behind the SDK's `TriggerAdapter` shape - * (`lousho/triggers`), so `POST /agents/:id/message` + * (`@linuxdevil/lousho/triggers`), so `POST /agents/:id/message` * (app.ts) is registered as `'chat'` in a `TriggerRegistry` alongside the * webhook/cron/Slack built-ins, instead of being a bespoke, unregistered * code path - closing the last of the four ad hoc "ways an agent can be @@ -40,8 +40,8 @@ * if ever actually called, rather than silently no-op-ing; `trigger()` is * the real, per-request entrypoint `app.ts` uses. */ -import type { AgentSpecV2, ExecutionResult } from 'lousho'; -import type { RunnableAgent, TriggerAdapter, TriggerContext, TriggerHandle } from 'lousho/triggers'; +import type { AgentSpecV2, ExecutionResult } from '@linuxdevil/lousho'; +import type { RunnableAgent, TriggerAdapter, TriggerContext, TriggerHandle } from '@linuxdevil/lousho/triggers'; import type { RunManager } from './runRegistry'; export class ChatTriggerAdapter implements TriggerAdapter { diff --git a/apps/agent-forge/server/checkpointStore.ts b/apps/agent-forge/server/checkpointStore.ts index ed90e2e76..6f17ed377 100644 --- a/apps/agent-forge/server/checkpointStore.ts +++ b/apps/agent-forge/server/checkpointStore.ts @@ -35,7 +35,7 @@ import { type CheckpointHistoryEntry, type CheckpointHistoryOptions, type CheckpointStore, -} from 'lousho'; +} from '@linuxdevil/lousho'; /** Write to a temp file and rename it over `file`, so a crash never leaves half a file there. */ function writeFileAtomic(file: string, content: string): void { diff --git a/apps/agent-forge/server/cli.ts b/apps/agent-forge/server/cli.ts new file mode 100644 index 000000000..82eb9a919 --- /dev/null +++ b/apps/agent-forge/server/cli.ts @@ -0,0 +1,142 @@ +/** + * `lousho-studio` - the packaged Studio's own command. + * + * Bundled by `server/tsup.config.ts` to `dist-server/cli.cjs` (the package's + * `bin`), it parses its flags, starts the same `startStudioServer()` the + * `lousho studio` launcher spawns, prints the tokened URL and shuts down + * cleanly on SIGINT/SIGTERM. + * + * Usage: lousho-studio [--workspace ] [--port N] [--host H] + * + * Options: + * --workspace a project folder to open (default: the current folder) + * --port N the port (default 4750) + * --host H the interface to listen on (default 127.0.0.1) + * -v, --version print the version + * -h, --help show this help + */ +import * as fs from 'node:fs'; +import * as path from 'node:path'; +import { parseArgs } from 'node:util'; +import { fileURLToPath, pathToFileURL } from 'node:url'; +import { shutdownOnSignals, startStudioServer } from './index'; + +export const USAGE = `Usage: lousho-studio [--workspace ] [--port N] [--host H] + +Opens Lousho Studio. It prints a URL with a per-launch token; every API and WebSocket request needs that token. + +Options: + --workspace a project folder to open (default: the current folder); it is added to your workspaces + --port N the port (default 4750) + --host H the interface to listen on (default 127.0.0.1) + -v, --version print the version + -h, --help show this help`; + +export interface StudioCliArgs { + workspace?: string; + port?: number; + host?: string; + version?: boolean; + help?: boolean; +} + +/** Thrown for a bad flag/value: the message is printed after `lousho-studio: ` with the usage. */ +export class StudioCliUsageError extends Error {} + +function parsePort(value: string | undefined): number | undefined { + if (value === undefined) return undefined; + const port = Number(value); + if (!Number.isInteger(port) || port < 0 || port > 65535) { + throw new StudioCliUsageError('--port must be an integer between 0 and 65535.'); + } + return port; +} + +/** Parses `lousho-studio` argv (without the node/script entries). Throws StudioCliUsageError on bad input. */ +export function parseStudioCliArgs(argv: string[]): StudioCliArgs { + let values: Record; + try { + ({ values } = parseArgs({ + args: argv, + strict: true, + allowPositionals: false, + options: { + workspace: { type: 'string' }, + port: { type: 'string' }, + host: { type: 'string' }, + version: { type: 'boolean', short: 'v' }, + help: { type: 'boolean', short: 'h' }, + }, + })); + } catch (error) { + throw new StudioCliUsageError((error as Error).message); + } + const workspace = values.workspace as string | undefined; + const host = values.host as string | undefined; + const port = parsePort(values.port as string | undefined); + return { + ...(workspace !== undefined ? { workspace } : {}), + ...(port !== undefined ? { port } : {}), + ...(host !== undefined ? { host } : {}), + ...(values.version === true ? { version: true } : {}), + ...(values.help === true ? { help: true } : {}), + }; +} + +/** The installed package's version: `../package.json` from both `server/` (tsx) and `dist-server/` (the bundle). */ +export function studioVersion(moduleDir: string): string { + const pkg = JSON.parse(fs.readFileSync(path.join(moduleDir, '..', 'package.json'), 'utf8')) as { version?: string }; + return pkg.version ?? '0.0.0'; +} + +async function main(): Promise { + let args: StudioCliArgs; + try { + args = parseStudioCliArgs(process.argv.slice(2)); + } catch (error) { + console.error(`lousho-studio: ${(error as Error).message}`); + console.error(USAGE); + process.exit(2); + } + const moduleDir = path.dirname(fileURLToPath(import.meta.url)); + if (args.help) { + console.log(USAGE); + return; + } + if (args.version) { + console.log(studioVersion(moduleDir)); + return; + } + let workspaceDir = process.cwd(); + if (args.workspace !== undefined) { + workspaceDir = path.resolve(args.workspace); + if (!fs.existsSync(workspaceDir) || !fs.statSync(workspaceDir).isDirectory()) { + console.error(`lousho-studio: --workspace '${args.workspace}' is not a folder.`); + process.exit(2); + } + } + const port = args.port ?? 4750; + const host = args.host ?? '127.0.0.1'; + let handle; + try { + handle = await startStudioServer({ port, host, baseDir: workspaceDir }); + } catch (error) { + const message = error instanceof Error ? error.message : String(error); + if (/already in use/.test(message)) { + console.error(`lousho-studio: port ${port} is in use. Is Studio already running? Start this one with --port ${port + 1}.`); + } else { + console.error(`lousho-studio: ${message}`); + } + process.exit(1); + } + console.log(`Lousho Studio ${studioVersion(moduleDir)}: ${handle.url}`); + console.log(`Workspace: ${workspaceDir}`); + shutdownOnSignals(handle); +} + +const isMainModule = + process.argv[1] !== undefined && import.meta.url === pathToFileURL(process.argv[1]).href; + +if (isMainModule) { + void main(); +} diff --git a/apps/agent-forge/server/collector.ts b/apps/agent-forge/server/collector.ts index 400f3acb4..30b625d44 100644 --- a/apps/agent-forge/server/collector.ts +++ b/apps/agent-forge/server/collector.ts @@ -23,8 +23,8 @@ import { type HealthProbe, type ObserveDaemon, type ObserveDaemonOptions, -} from 'lousho/observe'; -import { VERSION } from 'lousho'; +} from '@linuxdevil/lousho/observe'; +import { VERSION } from '@linuxdevil/lousho'; import type { CollectorStatus, CollectorUpdate } from '../shared/wireTypes'; import type { SecretsStore } from './secretsStore'; import type { SettingsStore } from './settingsStore'; diff --git a/apps/agent-forge/server/compileHooks.test.ts b/apps/agent-forge/server/compileHooks.test.ts index fbe922e0d..73566e6eb 100644 --- a/apps/agent-forge/server/compileHooks.test.ts +++ b/apps/agent-forge/server/compileHooks.test.ts @@ -1,6 +1,6 @@ import { describe, expect, it, vi } from 'vitest'; -import { AgentExecutor, ToolRegistry, AgentBuilder } from 'lousho/executor'; -import { NoopSandbox, createMockProvider } from 'lousho'; +import { AgentExecutor, ToolRegistry, AgentBuilder } from '@linuxdevil/lousho/executor'; +import { NoopSandbox, createMockProvider } from '@linuxdevil/lousho'; import { compileHooksFromSpecPolicy, isSerializedHookList, type SerializedHook } from './compileHooks'; describe('isSerializedHookList', () => { diff --git a/apps/agent-forge/server/compileHooks.ts b/apps/agent-forge/server/compileHooks.ts index 1a2c73478..4fba1295d 100644 --- a/apps/agent-forge/server/compileHooks.ts +++ b/apps/agent-forge/server/compileHooks.ts @@ -8,8 +8,8 @@ * `sandboxRunHook()` (hookSandbox.ts) rather than running the user's code * in this server process - see that file's doc comment for why. */ -import { HookRegistry, type AgentHook } from 'lousho'; -import type { SandboxAdapter, GenerateOptions, Message } from 'lousho'; +import { HookRegistry, type AgentHook } from '@linuxdevil/lousho'; +import type { SandboxAdapter, GenerateOptions, Message } from '@linuxdevil/lousho'; import { sandboxRunHook } from './hookSandbox'; /** Shape graphToSpec.ts serializes each hook as, under `spec.policy.hooks`. */ diff --git a/apps/agent-forge/server/debugController.ts b/apps/agent-forge/server/debugController.ts index 280e8122c..98395bc16 100644 --- a/apps/agent-forge/server/debugController.ts +++ b/apps/agent-forge/server/debugController.ts @@ -29,7 +29,7 @@ * only supported on `llm` and `tool` nodes, matching this app's graph model * (graph/types.ts) - see the epic report for the full breakdown. */ -import type { AgentHook, Message } from 'lousho'; +import type { AgentHook, Message } from '@linuxdevil/lousho'; export type DebugBoundary = 'before' | 'after'; diff --git a/apps/agent-forge/server/diff/__tests__/specChange.test.ts b/apps/agent-forge/server/diff/__tests__/specChange.test.ts index 749692f6c..736906506 100644 --- a/apps/agent-forge/server/diff/__tests__/specChange.test.ts +++ b/apps/agent-forge/server/diff/__tests__/specChange.test.ts @@ -1,5 +1,5 @@ import { describe, it, expect } from 'vitest'; -import type { AgentSpecV2 } from 'lousho'; +import type { AgentSpecV2 } from '@linuxdevil/lousho'; import { diffSpecs } from '../specChange'; import type { SpecSnapshot } from '../../../shared/wireTypes'; diff --git a/apps/agent-forge/server/hookSandbox.test.ts b/apps/agent-forge/server/hookSandbox.test.ts index 03ead7a37..5c77136cc 100644 --- a/apps/agent-forge/server/hookSandbox.test.ts +++ b/apps/agent-forge/server/hookSandbox.test.ts @@ -1,5 +1,5 @@ import { describe, expect, it } from 'vitest'; -import { NoopSandbox } from 'lousho'; +import { NoopSandbox } from '@linuxdevil/lousho'; import { sandboxRunHook } from './hookSandbox'; describe('sandboxRunHook', () => { diff --git a/apps/agent-forge/server/hookSandbox.ts b/apps/agent-forge/server/hookSandbox.ts index ea04e2069..9876a3f89 100644 --- a/apps/agent-forge/server/hookSandbox.ts +++ b/apps/agent-forge/server/hookSandbox.ts @@ -24,7 +24,7 @@ * chance to mediate it exactly like it would for a sandboxed tool. */ -import type { SandboxAdapter } from 'lousho'; +import type { SandboxAdapter } from '@linuxdevil/lousho'; /** Generic ctx payload shape a hook script receives/returns (HookContext family, minus non-serializable fields like `messages`/`toolCall`). */ export type HookSandboxCtx = Record; diff --git a/apps/agent-forge/server/host/agentHost.ts b/apps/agent-forge/server/host/agentHost.ts index d0bb14869..c41b1532d 100644 --- a/apps/agent-forge/server/host/agentHost.ts +++ b/apps/agent-forge/server/host/agentHost.ts @@ -24,7 +24,7 @@ import type { ExecutionResult, PendingApproval, Span, -} from 'lousho'; +} from '@linuxdevil/lousho'; import type { CatalogGuardrail, CatalogModel, CatalogTool, CatalogTriggerType } from '../../shared/wireTypes'; export interface AgentHost { diff --git a/apps/agent-forge/server/host/bridgeAgentHost.ts b/apps/agent-forge/server/host/bridgeAgentHost.ts index 6785b32b4..a67ff3bd5 100644 --- a/apps/agent-forge/server/host/bridgeAgentHost.ts +++ b/apps/agent-forge/server/host/bridgeAgentHost.ts @@ -12,7 +12,7 @@ * `HostReadOnlyError` except `catalog` (the in-process fallback) and * `dispose`. */ -import type { Message } from 'lousho'; +import type { Message } from '@linuxdevil/lousho'; import { BridgeError, type BridgeClient } from './bridgeClient'; import { providerEnv, secretsFor, toHostEvent } from './bridgeMapping'; import type { WorkspaceSdk } from './workspaceSdk'; diff --git a/apps/agent-forge/server/host/bridgeClient.ts b/apps/agent-forge/server/host/bridgeClient.ts index 24a798bd8..5989a1fac 100644 --- a/apps/agent-forge/server/host/bridgeClient.ts +++ b/apps/agent-forge/server/host/bridgeClient.ts @@ -24,7 +24,7 @@ import { type BridgeParams, type BridgePush, type BridgeResults, -} from 'lousho'; +} from '@linuxdevil/lousho'; /** Lifecycle of the bridge child this client owns. */ export type BridgeState = 'idle' | 'starting' | 'ready' | 'restarting' | 'failed' | 'unsupported' | 'stopped'; diff --git a/apps/agent-forge/server/host/bridgeMapping.ts b/apps/agent-forge/server/host/bridgeMapping.ts index c1cb777b5..c36b71987 100644 --- a/apps/agent-forge/server/host/bridgeMapping.ts +++ b/apps/agent-forge/server/host/bridgeMapping.ts @@ -9,7 +9,7 @@ * resolves them by name in the project, so Studio ships exactly the * names the spec mentions, nothing more. */ -import type { AgentSpecV2, BridgePush, BridgePushData } from 'lousho'; +import type { AgentSpecV2, BridgePush, BridgePushData } from '@linuxdevil/lousho'; import type { HostEvent, HostRunResult } from './agentHost'; import type { SecretsStore } from '../secretsStore'; diff --git a/apps/agent-forge/server/host/hostSelector.ts b/apps/agent-forge/server/host/hostSelector.ts index 27340fbf5..54642d7af 100644 --- a/apps/agent-forge/server/host/hostSelector.ts +++ b/apps/agent-forge/server/host/hostSelector.ts @@ -1,249 +1,249 @@ -/** - * WorkspaceHostSelector: picks a workspace's `AgentHost` once, from disk, - * then finishes the decision after the `hello` handshake (PLAN §19.4 - * "Selection per workspace"). - * - * Synchronous part (`createWorkspaceContext` cannot await): `findWorkspaceSdk` - * decides rows 1-2 immediately - no SDK, or an unbuilt SDK checkout, means - * Studio's in-process host and no child process is ever spawned. Rows 3-7 - * need the handshake, so the selector exposes a provisional `bridge` - * selection and `ready()` settles it. - * - * `LOUSHO_OBSERVE_PROJECT = ref.id` in the child env makes the project's - * runs group under this workspace on the observe daemon (the daemon's - * default project is the package name, which would not match); the studio - * token and listener vars are stripped so project code never sees them. - */ -import type { WorkspaceRef } from '../../shared/workspace'; -import { BRIDGE_PROTOCOL_VERSION } from 'lousho'; -import { observeTarget } from '../observeProxy'; -import type { HostBanner, HostMode } from '../../shared/wireTypes'; -import { BridgeClient, BridgeError, type BridgeClientOptions, type BridgeState } from './bridgeClient'; -import { BridgeAgentHost } from './bridgeAgentHost'; -import { InProcessAgentHost } from './inProcessAgentHost'; -import { MIN_BRIDGE_SDK_VERSION } from './sdkCompat'; -import { findWorkspaceSdk, type WorkspaceSdk } from './workspaceSdk'; -import type { AgentHost } from './agentHost'; -import type { SecretsStore } from '../secretsStore'; - -/** Where a workspace's runs execute, plus the banner explaining it. */ -export interface HostSelection { - mode: HostMode; - /** The SDK found on disk (bridge/read-only rows); absent in-process. */ - sdk?: WorkspaceSdk; - /** The SDK version runs execute under, or null before it is known. */ - sdkVersion: string | null; - banner: HostBanner | null; -} - -/** Everything the selector needs that the workspace context cannot build synchronously. */ -export interface SelectHostDeps { - /** Builds the in-process host from this workspace's stores (rows 1-2). */ - inProcess: () => InProcessAgentHost; - secretsStore: SecretsStore; - /** The observe collector (W12's CollectorManager; env fallback until then). */ - collector: () => { url: string; token?: string }; - /** Studio's own SDK version, for `hello.client` is implicit; banners name it. */ - studioVersion: string; - /** Test hooks forwarded to `BridgeClient` (fake spawn, short timeouts). */ - clientOptions?: Partial; - /** Test hooks: Node flags before the bin (e.g. `--import` a provider preload). */ - nodeArgs?: readonly string[]; - /** Test hooks: merged last into the child env (e.g. `SCRIPTED_TURNS`). */ - extraEnv?: Readonly>; -} - -const INFO_NO_SDK = (v: string): HostBanner => ({ - tone: 'info', - text: `This project has no @lousho/build-ai-agent installed. Runs use Studio's built-in SDK ${v}.`, -}); -const INFO_UNBUILT = (v: string): HostBanner => ({ - tone: 'info', - text: `This SDK checkout is not built, so runs use Studio's built-in SDK ${v}. Run npm run build to use it.`, -}); -const WARNING_OLD_SDK = (): HostBanner => ({ - tone: 'warning', - text: `Update @lousho/build-ai-agent in this project to ${MIN_BRIDGE_SDK_VERSION} or later to edit agents here.`, -}); -const WARNING_NEWER_SDK = (v: string): HostBanner => ({ - tone: 'warning', - text: `This project's @lousho/build-ai-agent ${v} is newer than this Studio understands. Update Studio to edit agents here.`, -}); -const DANGER_CRASHED = (): HostBanner => ({ - tone: 'danger', - text: "This project's agent process keeps stopping. Check the Studio terminal for its output, then restart it.", - action: 'restart', -}); - -/** - * The pre-W12 collector: the observe daemon Studio itself is configured - * against (`LOUSHO_OBSERVE_URL`, default loopback :7777) plus its bearer - * when set. W12's CollectorManager replaces this behind the same - * `() => { url, token? }` shape. - */ -export function envCollector(): { url: string; token?: string } { - const token = process.env.LOUSHO_OBSERVE_TOKEN; - return { url: observeTarget(), ...(token ? { token } : {}) }; -} - -/** Env the bridge child gets: observe reporting + workspace identity, minus Studio's own secrets/listeners. */ -function bridgeEnv(ref: WorkspaceRef, collector: { url: string; token?: string }, extra?: Readonly>): NodeJS.ProcessEnv { - const env: NodeJS.ProcessEnv = { - ...process.env, - LOUSHO_OBSERVE: collector.url, - LOUSHO_OBSERVE_PROJECT: ref.id, - LOUSHO_BRIDGE: '1', - }; - if (collector.token) env.LOUSHO_OBSERVE_TOKEN = collector.token; - else delete env.LOUSHO_OBSERVE_TOKEN; - for (const key of ['LOUSHO_STUDIO_TOKEN', 'LOUSHO_STUDIO_ALLOWED_ORIGINS', 'PORT', 'HOST', 'BASE_DIR', 'NO_STATIC', 'STATIC_DIR']) { - delete env[key]; - } - return { ...env, ...extra }; -} - -/** One workspace's host decision: in-process, bridge, or read-only. */ -export class WorkspaceHostSelector { - /** The host RunManager uses - fixed for the context's life. */ - readonly host: AgentHost; - /** RunManager's `observe` flag: true only in-process (a bridge reports to the daemon itself). */ - readonly reportsToObserve: boolean; - /** The bridge client on rows 3-7, for W11c's state and stderr. */ - readonly client?: BridgeClient; - - private readonly sdk: WorkspaceSdk | undefined; - private readonly inProcessHost?: InProcessAgentHost; - private fallbackHost: InProcessAgentHost | undefined; - private readonly listeners = new Set<(selection: HostSelection) => void>(); - private selection: HostSelection; - private readyPromise: Promise | undefined; - - constructor(ref: WorkspaceRef, deps: SelectHostDeps) { - this.sdk = findWorkspaceSdk(ref.root); - if (this.sdk === undefined) { - // Row 1: no SDK on disk - Studio's own SDK runs everything. - this.inProcessHost = deps.inProcess(); - this.host = this.inProcessHost; - this.reportsToObserve = true; - this.selection = { mode: 'in-process', sdkVersion: deps.studioVersion, banner: INFO_NO_SDK(deps.studioVersion) }; - return; - } - if (this.sdk.source === 'self' && !this.sdk.built) { - // Row 2: the SDK checkout itself, unbuilt - same fallback, different hint. - this.inProcessHost = deps.inProcess(); - this.host = this.inProcessHost; - this.reportsToObserve = true; - this.selection = { mode: 'in-process', sdkVersion: deps.studioVersion, banner: INFO_UNBUILT(deps.studioVersion) }; - return; - } - // Rows 3-7: a BridgeAgentHost over a BridgeClient; `ready()` settles which. - const client = new BridgeClient( - { - command: process.execPath, - args: [...(deps.nodeArgs ?? []), this.sdk.binPath, 'bridge'], - cwd: ref.root, - env: bridgeEnv(ref, deps.collector(), deps.extraEnv), - label: ref.name, - }, - { clientVersion: deps.studioVersion, ...deps.clientOptions } - ); - this.client = client; - this.reportsToObserve = false; - const bridgeHost = new BridgeAgentHost(client, { - secretsStore: deps.secretsStore, - ready: () => this.ready(), - fallbackCatalog: () => (this.fallbackHost ??= deps.inProcess()).catalog(), - sdk: this.sdk, - }); - this.host = bridgeHost; - // Provisional: the handshake has not run; W11c shows 'starting'. - this.selection = { mode: 'bridge', sdk: this.sdk, sdkVersion: this.sdk.version, banner: null }; - client.onStateChange((state) => this.onClientState(state)); - } - - /** The current selection snapshot (pre-handshake rows 3-7 read `bridge`/`sdk.version`). */ - current(): HostSelection { - return this.selection; - } - - /** Memoized handshake + row check; in-process resolves at once. */ - ready(): Promise { - if (this.inProcessHost !== undefined) return Promise.resolve(this.selection); - this.readyPromise ??= this.finishHandshake(false); - return this.readyPromise; - } - - /** Clears the memo, `client.restart()`, and re-runs the row check (W11c's Restart). */ - // fallow-ignore-next-line unused-class-member -- W11c's status banner restart action consumes it; W11b only constructs the selector - restart(): Promise { - if (this.client === undefined) return Promise.resolve(this.selection); - this.readyPromise = this.finishHandshake(true); - return this.readyPromise; - } - - /** Subscribes to selection changes (W11c turns them into `host.status` events). */ - onChange(listener: (selection: HostSelection) => void): () => void { - this.listeners.add(listener); - return () => this.listeners.delete(listener); - } - - // -- handshake rows --------------------------------------------------------- - - private async finishHandshake(useRestart: boolean): Promise { - const client = this.client; - if (client === undefined) return this.selection; - try { - const hello = useRestart ? await client.restart() : await client.start(); - // Rows 4-5: a hello that cannot serve v2 specs or protocol 1 is read-only. - // The handshake's version is the SDK that answered - record it over the disk read. - if (!hello.specVersions.includes(2)) { - await client.stop(); - return this.settle({ mode: 'read-only', banner: WARNING_OLD_SDK(), sdkVersion: hello.sdkVersion }); - } - if (hello.protocol !== BRIDGE_PROTOCOL_VERSION) { - await client.stop(); - return this.settle({ mode: 'read-only', banner: WARNING_NEWER_SDK(hello.sdkVersion), sdkVersion: hello.sdkVersion }); - } - // Row 6. - return this.settle({ mode: 'bridge', banner: null, sdkVersion: hello.sdkVersion }); - } catch (error) { - if (error instanceof BridgeError && error.code === 'LOUSHO_BRIDGE_UNSUPPORTED') { - // Row 3: no hello ever answered - the SDK version is unknown. - await client.stop(); - return this.settle({ mode: 'read-only', banner: WARNING_OLD_SDK(), sdkVersion: null }); - } - // Row 7: transport-level failure - the host stays 'bridge' with a restart banner. - return this.settle({ mode: 'bridge', banner: DANGER_CRASHED() }); - } - } - - /** A later crash flips a healthy bridge to the restart banner; recovery clears it. */ - private onClientState(state: BridgeState): void { - if (this.selection.mode !== 'bridge') return; - if (state === 'failed' && this.selection.banner === null) { - this.settle({ mode: 'bridge', banner: DANGER_CRASHED() }); - } else if (state === 'ready' && this.selection.banner?.action === 'restart') { - this.settle({ mode: 'bridge', banner: null }); - } - } - - private settle(patch: { mode: HostMode; banner: HostBanner | null; sdkVersion?: string | null }): HostSelection { - const next: HostSelection = { - mode: patch.mode, - sdk: this.sdk, - sdkVersion: patch.sdkVersion !== undefined ? patch.sdkVersion : this.selection.sdkVersion, - banner: patch.banner, - }; - const before = this.selection; - const unchanged = - before.mode === next.mode && - before.sdkVersion === next.sdkVersion && - before.banner?.tone === next.banner?.tone && - before.banner?.text === next.banner?.text && - before.banner?.action === next.banner?.action; - if (unchanged) return before; - this.selection = next; - for (const listener of this.listeners) listener(this.selection); - return this.selection; - } -} +/** + * WorkspaceHostSelector: picks a workspace's `AgentHost` once, from disk, + * then finishes the decision after the `hello` handshake (PLAN §19.4 + * "Selection per workspace"). + * + * Synchronous part (`createWorkspaceContext` cannot await): `findWorkspaceSdk` + * decides rows 1-2 immediately - no SDK, or an unbuilt SDK checkout, means + * Studio's in-process host and no child process is ever spawned. Rows 3-7 + * need the handshake, so the selector exposes a provisional `bridge` + * selection and `ready()` settles it. + * + * `LOUSHO_OBSERVE_PROJECT = ref.id` in the child env makes the project's + * runs group under this workspace on the observe daemon (the daemon's + * default project is the package name, which would not match); the studio + * token and listener vars are stripped so project code never sees them. + */ +import type { WorkspaceRef } from '../../shared/workspace'; +import { BRIDGE_PROTOCOL_VERSION } from '@linuxdevil/lousho'; +import { observeTarget } from '../observeProxy'; +import type { HostBanner, HostMode } from '../../shared/wireTypes'; +import { BridgeClient, BridgeError, type BridgeClientOptions, type BridgeState } from './bridgeClient'; +import { BridgeAgentHost } from './bridgeAgentHost'; +import { InProcessAgentHost } from './inProcessAgentHost'; +import { MIN_BRIDGE_SDK_VERSION } from './sdkCompat'; +import { findWorkspaceSdk, type WorkspaceSdk } from './workspaceSdk'; +import type { AgentHost } from './agentHost'; +import type { SecretsStore } from '../secretsStore'; + +/** Where a workspace's runs execute, plus the banner explaining it. */ +export interface HostSelection { + mode: HostMode; + /** The SDK found on disk (bridge/read-only rows); absent in-process. */ + sdk?: WorkspaceSdk; + /** The SDK version runs execute under, or null before it is known. */ + sdkVersion: string | null; + banner: HostBanner | null; +} + +/** Everything the selector needs that the workspace context cannot build synchronously. */ +export interface SelectHostDeps { + /** Builds the in-process host from this workspace's stores (rows 1-2). */ + inProcess: () => InProcessAgentHost; + secretsStore: SecretsStore; + /** The observe collector (W12's CollectorManager; env fallback until then). */ + collector: () => { url: string; token?: string }; + /** Studio's own SDK version, for `hello.client` is implicit; banners name it. */ + studioVersion: string; + /** Test hooks forwarded to `BridgeClient` (fake spawn, short timeouts). */ + clientOptions?: Partial; + /** Test hooks: Node flags before the bin (e.g. `--import` a provider preload). */ + nodeArgs?: readonly string[]; + /** Test hooks: merged last into the child env (e.g. `SCRIPTED_TURNS`). */ + extraEnv?: Readonly>; +} + +const INFO_NO_SDK = (v: string): HostBanner => ({ + tone: 'info', + text: `This project has no @linuxdevil/lousho installed. Runs use Studio's built-in SDK ${v}.`, +}); +const INFO_UNBUILT = (v: string): HostBanner => ({ + tone: 'info', + text: `This SDK checkout is not built, so runs use Studio's built-in SDK ${v}. Run npm run build to use it.`, +}); +const WARNING_OLD_SDK = (): HostBanner => ({ + tone: 'warning', + text: `Update @linuxdevil/lousho in this project to ${MIN_BRIDGE_SDK_VERSION} or later to edit agents here.`, +}); +const WARNING_NEWER_SDK = (v: string): HostBanner => ({ + tone: 'warning', + text: `This project's @linuxdevil/lousho ${v} is newer than this Studio understands. Update Studio to edit agents here.`, +}); +const DANGER_CRASHED = (): HostBanner => ({ + tone: 'danger', + text: "This project's agent process keeps stopping. Check the Studio terminal for its output, then restart it.", + action: 'restart', +}); + +/** + * The pre-W12 collector: the observe daemon Studio itself is configured + * against (`LOUSHO_OBSERVE_URL`, default loopback :7777) plus its bearer + * when set. W12's CollectorManager replaces this behind the same + * `() => { url, token? }` shape. + */ +export function envCollector(): { url: string; token?: string } { + const token = process.env.LOUSHO_OBSERVE_TOKEN; + return { url: observeTarget(), ...(token ? { token } : {}) }; +} + +/** Env the bridge child gets: observe reporting + workspace identity, minus Studio's own secrets/listeners. */ +function bridgeEnv(ref: WorkspaceRef, collector: { url: string; token?: string }, extra?: Readonly>): NodeJS.ProcessEnv { + const env: NodeJS.ProcessEnv = { + ...process.env, + LOUSHO_OBSERVE: collector.url, + LOUSHO_OBSERVE_PROJECT: ref.id, + LOUSHO_BRIDGE: '1', + }; + if (collector.token) env.LOUSHO_OBSERVE_TOKEN = collector.token; + else delete env.LOUSHO_OBSERVE_TOKEN; + for (const key of ['LOUSHO_STUDIO_TOKEN', 'LOUSHO_STUDIO_ALLOWED_ORIGINS', 'PORT', 'HOST', 'BASE_DIR', 'NO_STATIC', 'STATIC_DIR']) { + delete env[key]; + } + return { ...env, ...extra }; +} + +/** One workspace's host decision: in-process, bridge, or read-only. */ +export class WorkspaceHostSelector { + /** The host RunManager uses - fixed for the context's life. */ + readonly host: AgentHost; + /** RunManager's `observe` flag: true only in-process (a bridge reports to the daemon itself). */ + readonly reportsToObserve: boolean; + /** The bridge client on rows 3-7, for W11c's state and stderr. */ + readonly client?: BridgeClient; + + private readonly sdk: WorkspaceSdk | undefined; + private readonly inProcessHost?: InProcessAgentHost; + private fallbackHost: InProcessAgentHost | undefined; + private readonly listeners = new Set<(selection: HostSelection) => void>(); + private selection: HostSelection; + private readyPromise: Promise | undefined; + + constructor(ref: WorkspaceRef, deps: SelectHostDeps) { + this.sdk = findWorkspaceSdk(ref.root); + if (this.sdk === undefined) { + // Row 1: no SDK on disk - Studio's own SDK runs everything. + this.inProcessHost = deps.inProcess(); + this.host = this.inProcessHost; + this.reportsToObserve = true; + this.selection = { mode: 'in-process', sdkVersion: deps.studioVersion, banner: INFO_NO_SDK(deps.studioVersion) }; + return; + } + if (this.sdk.source === 'self' && !this.sdk.built) { + // Row 2: the SDK checkout itself, unbuilt - same fallback, different hint. + this.inProcessHost = deps.inProcess(); + this.host = this.inProcessHost; + this.reportsToObserve = true; + this.selection = { mode: 'in-process', sdkVersion: deps.studioVersion, banner: INFO_UNBUILT(deps.studioVersion) }; + return; + } + // Rows 3-7: a BridgeAgentHost over a BridgeClient; `ready()` settles which. + const client = new BridgeClient( + { + command: process.execPath, + args: [...(deps.nodeArgs ?? []), this.sdk.binPath, 'bridge'], + cwd: ref.root, + env: bridgeEnv(ref, deps.collector(), deps.extraEnv), + label: ref.name, + }, + { clientVersion: deps.studioVersion, ...deps.clientOptions } + ); + this.client = client; + this.reportsToObserve = false; + const bridgeHost = new BridgeAgentHost(client, { + secretsStore: deps.secretsStore, + ready: () => this.ready(), + fallbackCatalog: () => (this.fallbackHost ??= deps.inProcess()).catalog(), + sdk: this.sdk, + }); + this.host = bridgeHost; + // Provisional: the handshake has not run; W11c shows 'starting'. + this.selection = { mode: 'bridge', sdk: this.sdk, sdkVersion: this.sdk.version, banner: null }; + client.onStateChange((state) => this.onClientState(state)); + } + + /** The current selection snapshot (pre-handshake rows 3-7 read `bridge`/`sdk.version`). */ + current(): HostSelection { + return this.selection; + } + + /** Memoized handshake + row check; in-process resolves at once. */ + ready(): Promise { + if (this.inProcessHost !== undefined) return Promise.resolve(this.selection); + this.readyPromise ??= this.finishHandshake(false); + return this.readyPromise; + } + + /** Clears the memo, `client.restart()`, and re-runs the row check (W11c's Restart). */ + // fallow-ignore-next-line unused-class-member -- W11c's status banner restart action consumes it; W11b only constructs the selector + restart(): Promise { + if (this.client === undefined) return Promise.resolve(this.selection); + this.readyPromise = this.finishHandshake(true); + return this.readyPromise; + } + + /** Subscribes to selection changes (W11c turns them into `host.status` events). */ + onChange(listener: (selection: HostSelection) => void): () => void { + this.listeners.add(listener); + return () => this.listeners.delete(listener); + } + + // -- handshake rows --------------------------------------------------------- + + private async finishHandshake(useRestart: boolean): Promise { + const client = this.client; + if (client === undefined) return this.selection; + try { + const hello = useRestart ? await client.restart() : await client.start(); + // Rows 4-5: a hello that cannot serve v2 specs or protocol 1 is read-only. + // The handshake's version is the SDK that answered - record it over the disk read. + if (!hello.specVersions.includes(2)) { + await client.stop(); + return this.settle({ mode: 'read-only', banner: WARNING_OLD_SDK(), sdkVersion: hello.sdkVersion }); + } + if (hello.protocol !== BRIDGE_PROTOCOL_VERSION) { + await client.stop(); + return this.settle({ mode: 'read-only', banner: WARNING_NEWER_SDK(hello.sdkVersion), sdkVersion: hello.sdkVersion }); + } + // Row 6. + return this.settle({ mode: 'bridge', banner: null, sdkVersion: hello.sdkVersion }); + } catch (error) { + if (error instanceof BridgeError && error.code === 'LOUSHO_BRIDGE_UNSUPPORTED') { + // Row 3: no hello ever answered - the SDK version is unknown. + await client.stop(); + return this.settle({ mode: 'read-only', banner: WARNING_OLD_SDK(), sdkVersion: null }); + } + // Row 7: transport-level failure - the host stays 'bridge' with a restart banner. + return this.settle({ mode: 'bridge', banner: DANGER_CRASHED() }); + } + } + + /** A later crash flips a healthy bridge to the restart banner; recovery clears it. */ + private onClientState(state: BridgeState): void { + if (this.selection.mode !== 'bridge') return; + if (state === 'failed' && this.selection.banner === null) { + this.settle({ mode: 'bridge', banner: DANGER_CRASHED() }); + } else if (state === 'ready' && this.selection.banner?.action === 'restart') { + this.settle({ mode: 'bridge', banner: null }); + } + } + + private settle(patch: { mode: HostMode; banner: HostBanner | null; sdkVersion?: string | null }): HostSelection { + const next: HostSelection = { + mode: patch.mode, + sdk: this.sdk, + sdkVersion: patch.sdkVersion !== undefined ? patch.sdkVersion : this.selection.sdkVersion, + banner: patch.banner, + }; + const before = this.selection; + const unchanged = + before.mode === next.mode && + before.sdkVersion === next.sdkVersion && + before.banner?.tone === next.banner?.tone && + before.banner?.text === next.banner?.text && + before.banner?.action === next.banner?.action; + if (unchanged) return before; + this.selection = next; + for (const listener of this.listeners) listener(this.selection); + return this.selection; + } +} diff --git a/apps/agent-forge/server/host/inProcessAgentHost.ts b/apps/agent-forge/server/host/inProcessAgentHost.ts index ce0cc7252..77fb01482 100644 --- a/apps/agent-forge/server/host/inProcessAgentHost.ts +++ b/apps/agent-forge/server/host/inProcessAgentHost.ts @@ -24,7 +24,7 @@ import { VERSION, type AgentRun, type ExecutionResult, -} from 'lousho'; +} from '@linuxdevil/lousho'; import { buildRunnableAgent, type RunWiring, type StudioRunDeps } from '../buildAgentV2'; import { HostRunNotActiveError, diff --git a/apps/agent-forge/server/host/sdkCompat.ts b/apps/agent-forge/server/host/sdkCompat.ts index 897a81c74..d06e867fe 100644 --- a/apps/agent-forge/server/host/sdkCompat.ts +++ b/apps/agent-forge/server/host/sdkCompat.ts @@ -1,10 +1,10 @@ -/** - * sdkCompat: the minimum `@lousho/build-ai-agent` version whose `lousho - * bridge` speaks protocol 1 (W10a/W10b). Studio and the SDK ship from the - * same repo, so the floor is this package's own version at the merge - - * W15b's compatibility matrix is the consumer that revisits it. - */ -import { VERSION } from 'lousho'; - -/** First SDK version that ships `lousho bridge`. */ -export const MIN_BRIDGE_SDK_VERSION = VERSION; +/** + * sdkCompat: the minimum `@linuxdevil/lousho` version whose `lousho + * bridge` speaks protocol 1 (W10a/W10b). Studio and the SDK ship from the + * same repo, so the floor is this package's own version at the merge - + * W15b's compatibility matrix is the consumer that revisits it. + */ +import { VERSION } from '@linuxdevil/lousho'; + +/** First SDK version that ships `lousho bridge`. */ +export const MIN_BRIDGE_SDK_VERSION = VERSION; diff --git a/apps/agent-forge/server/host/workspaceSdk.ts b/apps/agent-forge/server/host/workspaceSdk.ts index 5f0fd64a1..4459564bb 100644 --- a/apps/agent-forge/server/host/workspaceSdk.ts +++ b/apps/agent-forge/server/host/workspaceSdk.ts @@ -1,100 +1,101 @@ -/** - * workspaceSdk: finds the SDK install a workspace's runs should execute - * under (PLAN §19.4 "Selection per workspace"). - * - * The lookup is Node's own algorithm done by hand: the SDK's `exports` - * map does not expose `./package.json`, so `require.resolve( - * '@lousho/build-ai-agent/package.json')` throws ERR_PACKAGE_PATH_NOT_EXPORTED - * on every supported version. Walking `node_modules` upwards from the - * workspace root (nearest hit wins) matches what `import` would resolve. - * - * Two package names are accepted: `@lousho/build-ai-agent`, the scoped - * name the packaging waves (W13a/W13b) move to, and `lousho`, the name - * the package ships under today - a project on either gets its own SDK - * bridged. A workspace that IS an SDK checkout (`source: 'self'`) uses - * its own `bin/lousho.js`; `built` tells the selector whether `dist/` - * exists - an unbuilt checkout falls back to Studio's in-process host. - */ -import { existsSync, readFileSync, realpathSync } from 'node:fs'; -import { join, parse } from 'node:path'; - -/** Package names the SDK answers to: the scoped future name, then today's `lousho`. */ -const SDK_PACKAGE_NAMES = ['@lousho/build-ai-agent', 'lousho'] as const; - -/** The project's own SDK install, as the bridge spawns it. */ -export interface WorkspaceSdk { - /** The SDK's `package.json` `version`. */ - version: string; - /** Real path of the SDK package folder. */ - packageDir: string; - /** `/bin/lousho.js` - spawned as `node bridge`. */ - binPath: string; - /** `'self'` when the workspace root IS the SDK checkout, else `'node_modules'`. */ - source: 'node_modules' | 'self'; - /** `/dist/cli/bridge.mjs` exists - the checkout has been built. */ - built: boolean; -} - -interface SdkPackageJson { - name?: string; - version?: string; -} - -function readPackageJson(dir: string): SdkPackageJson | undefined { - try { - return JSON.parse(readFileSync(join(dir, 'package.json'), 'utf8')) as SdkPackageJson; - } catch { - return undefined; - } -} - -function isSdkPackageJson(pkg: SdkPackageJson | undefined): boolean { - return pkg !== undefined && (SDK_PACKAGE_NAMES as readonly string[]).includes(pkg.name ?? ''); -} - -function sdkAt(packageDir: string, source: WorkspaceSdk['source']): WorkspaceSdk | undefined { - const binPath = join(packageDir, 'bin', 'lousho.js'); - if (!existsSync(binPath)) return undefined; - const version = readPackageJson(packageDir)?.version; - if (version === undefined) return undefined; - return { - version, - packageDir, - binPath, - source, - built: existsSync(join(packageDir, 'dist', 'cli', 'bridge.mjs')), - }; -} - -/** - * Finds the SDK a workspace runs under, or `undefined` when it has none - * (or the install lacks `bin/lousho.js`). Nearest `node_modules` hit - * wins; the workspace's own checkout wins over any install. - */ -export function findWorkspaceSdk(root: string): WorkspaceSdk | undefined { - let real: string; - try { - real = realpathSync(root); - } catch { - return undefined; - } - if (isSdkPackageJson(readPackageJson(real))) { - return sdkAt(real, 'self'); - } - for (let dir = real, prev = ''; dir !== prev; prev = dir, dir = parse(dir).dir) { - for (const name of SDK_PACKAGE_NAMES) { - const pkgDir = join(dir, 'node_modules', ...name.split('/')); - // The directory name alone is not proof: verify the manifest's own - // `name` (a junction to the SDK checkout carries `lousho` today, - // `@lousho/build-ai-agent` after the packaging rename). - if (!isSdkPackageJson(readPackageJson(pkgDir))) continue; - try { - const sdk = sdkAt(realpathSync(pkgDir), 'node_modules'); - if (sdk !== undefined) return sdk; - } catch { - // An unreadable symlinked install is not an SDK. - } - } - } - return undefined; -} +/** + * workspaceSdk: finds the SDK install a workspace's runs should execute + * under (PLAN §19.4 "Selection per workspace"). + * + * The lookup is Node's own algorithm done by hand: the SDK's `exports` + * map does not expose `./package.json`, so `require.resolve( + * '@lousho/build-ai-agent/package.json')` throws ERR_PACKAGE_PATH_NOT_EXPORTED + * on every supported version. Walking `node_modules` upwards from the + * workspace root (nearest hit wins) matches what `import` would resolve. + * + * Two package names are accepted: `@linuxdevil/lousho`, the scoped + * name the package ships under, and `lousho`, the pre-rename name kept + * for projects that still depend on it. + * the package ships under today - a project on either gets its own SDK + * bridged. A workspace that IS an SDK checkout (`source: 'self'`) uses + * its own `bin/lousho.js`; `built` tells the selector whether `dist/` + * exists - an unbuilt checkout falls back to Studio's in-process host. + */ +import { existsSync, readFileSync, realpathSync } from 'node:fs'; +import { join, parse } from 'node:path'; + +/** Package names the SDK answers to: the scoped future name, then today's `lousho`. */ +const SDK_PACKAGE_NAMES = ['@linuxdevil/lousho', 'lousho'] as const; + +/** The project's own SDK install, as the bridge spawns it. */ +export interface WorkspaceSdk { + /** The SDK's `package.json` `version`. */ + version: string; + /** Real path of the SDK package folder. */ + packageDir: string; + /** `/bin/lousho.js` - spawned as `node bridge`. */ + binPath: string; + /** `'self'` when the workspace root IS the SDK checkout, else `'node_modules'`. */ + source: 'node_modules' | 'self'; + /** `/dist/cli/bridge.mjs` exists - the checkout has been built. */ + built: boolean; +} + +interface SdkPackageJson { + name?: string; + version?: string; +} + +function readPackageJson(dir: string): SdkPackageJson | undefined { + try { + return JSON.parse(readFileSync(join(dir, 'package.json'), 'utf8')) as SdkPackageJson; + } catch { + return undefined; + } +} + +function isSdkPackageJson(pkg: SdkPackageJson | undefined): boolean { + return pkg !== undefined && (SDK_PACKAGE_NAMES as readonly string[]).includes(pkg.name ?? ''); +} + +function sdkAt(packageDir: string, source: WorkspaceSdk['source']): WorkspaceSdk | undefined { + const binPath = join(packageDir, 'bin', 'lousho.js'); + if (!existsSync(binPath)) return undefined; + const version = readPackageJson(packageDir)?.version; + if (version === undefined) return undefined; + return { + version, + packageDir, + binPath, + source, + built: existsSync(join(packageDir, 'dist', 'cli', 'bridge.mjs')), + }; +} + +/** + * Finds the SDK a workspace runs under, or `undefined` when it has none + * (or the install lacks `bin/lousho.js`). Nearest `node_modules` hit + * wins; the workspace's own checkout wins over any install. + */ +export function findWorkspaceSdk(root: string): WorkspaceSdk | undefined { + let real: string; + try { + real = realpathSync(root); + } catch { + return undefined; + } + if (isSdkPackageJson(readPackageJson(real))) { + return sdkAt(real, 'self'); + } + for (let dir = real, prev = ''; dir !== prev; prev = dir, dir = parse(dir).dir) { + for (const name of SDK_PACKAGE_NAMES) { + const pkgDir = join(dir, 'node_modules', ...name.split('/')); + // The directory name alone is not proof: verify the manifest's own + // `name` (a junction to the SDK checkout carries `@linuxdevil/lousho` + // or the legacy `lousho`). + if (!isSdkPackageJson(readPackageJson(pkgDir))) continue; + try { + const sdk = sdkAt(realpathSync(pkgDir), 'node_modules'); + if (sdk !== undefined) return sdk; + } catch { + // An unreadable symlinked install is not an SDK. + } + } + } + return undefined; +} diff --git a/apps/agent-forge/server/inbox/__tests__/inboxAggregate.test.ts b/apps/agent-forge/server/inbox/__tests__/inboxAggregate.test.ts index 89e0c128d..ea622809c 100644 --- a/apps/agent-forge/server/inbox/__tests__/inboxAggregate.test.ts +++ b/apps/agent-forge/server/inbox/__tests__/inboxAggregate.test.ts @@ -2,7 +2,7 @@ import { describe, it, expect, beforeEach, afterEach } from 'vitest'; import * as fs from 'node:fs'; import * as os from 'node:os'; import * as path from 'node:path'; -import type { AgentSpecV2 } from 'lousho'; +import type { AgentSpecV2 } from '@linuxdevil/lousho'; import { listAllInbox, countAllInbox, type InboxAggregateDeps } from '../inboxAggregate'; import { AlertStore } from '../alertStore'; import { WorkspaceRegistry, type WorkspaceEntry } from '../../workspace/workspaceRegistry'; diff --git a/apps/agent-forge/server/index.ts b/apps/agent-forge/server/index.ts index acdc2c905..5d3d2ee20 100644 --- a/apps/agent-forge/server/index.ts +++ b/apps/agent-forge/server/index.ts @@ -6,6 +6,7 @@ * for tests/embedding. */ import { once } from 'node:events'; +import * as fs from 'node:fs'; import * as http from 'node:http'; import * as path from 'node:path'; import { fileURLToPath, pathToFileURL } from 'node:url'; @@ -33,7 +34,8 @@ import { TemplateCatalog } from './templates/templateCatalog'; import { PassRunner } from './passes/passRunner'; import { SpendLog } from './passes/spendLog'; import { AlertStore } from './inbox/alertStore'; -import { VERSION } from 'lousho'; +import { VERSION } from '@linuxdevil/lousho'; +import { sdkRootDir } from './sdkRoot'; export interface StudioServerHandle { server: http.Server; @@ -143,14 +145,16 @@ function resolveStaticDir(staticDir: string | false | undefined, moduleDir: stri } /** - * The bundled SDK docs folder `searchDocs` reads: three levels above this - * file's dir lands on the package root (server/ and dist-server/ are both - * at apps/agent-forge/, same resolution as the default staticDir). - * `docs/*.md` ships in the package's `files`; a missing folder makes + * The bundled docs folder `searchDocs` reads. In the monorepo three levels + * above this file's dir lands on the repo root's `docs/`; in the packaged + * layout that path does not exist, so fall back to the SDK package's own + * `docs/` (which ships in its `files`). A missing folder makes * `searchDocs` answer UNAVAILABLE rather than fail. */ function resolveDocsDir(moduleDir: string): string { - return path.join(moduleDir, '..', '..', '..', 'docs'); + const repoDocs = path.join(moduleDir, '..', '..', '..', 'docs'); + if (fs.existsSync(repoDocs)) return repoDocs; + return path.join(sdkRootDir(moduleDir), 'docs'); } export async function startStudioServer( @@ -330,6 +334,15 @@ export async function startStudioServer( }; } +/** SIGINT/SIGTERM → `handle.close()` (collector, bridges, socket) then exit 0. Shared by both entry points. */ +export function shutdownOnSignals(handle: Pick): void { + const shutdown = () => { + void handle.close().finally(() => process.exit(0)); + }; + process.on('SIGINT', shutdown); + process.on('SIGTERM', shutdown); +} + // Allow `tsx server/index.ts` (or `node --import tsx server/index.ts`) to // boot the server directly, reading PORT/HOST/BASE_DIR from the // environment - this is how `lousho studio` (src/cli/studio.ts) launches it @@ -337,16 +350,20 @@ export async function startStudioServer( // so this module runs as real ESM under tsx - there is no CJS `require`/ // `module` to compare against, hence the `import.meta.url` check instead of // the usual `require.main === module` idiom. +// The basename check matters in the packaged bundle: `cli.cjs` inlines this +// module too, and without it `lousho-studio --help` would boot a second +// server on 4750 (argv[1] is cli.cjs there, not an index.* entry). const isMainModule = - process.argv[1] !== undefined && import.meta.url === pathToFileURL(process.argv[1]).href; + process.argv[1] !== undefined && + /^index\.(cjs|mjs|js|ts)$/.test(path.basename(process.argv[1])) && + import.meta.url === pathToFileURL(process.argv[1]).href; if (isMainModule) { const port = process.env.PORT ? Number(process.env.PORT) : undefined; const host = process.env.HOST; - const moduleDir = path.dirname(fileURLToPath(import.meta.url)); - // moduleDir is apps/agent-forge/server - the repo root (where `.lousho/` - // should live) is three levels up, unless BASE_DIR is set explicitly. - const baseDir = process.env.BASE_DIR ?? path.resolve(moduleDir, '..', '..', '..'); + // The launch workspace defaults to the folder the command ran in, unless + // BASE_DIR is set explicitly. + const baseDir = process.env.BASE_DIR ?? process.cwd(); // `src/cli/studio.ts` sets NO_STATIC=1 in dev mode (separate Vite dev // server process serves the UI there) and STATIC_DIR to override the // default `apps/agent-forge/dist` lookup in prod mode. @@ -368,11 +385,7 @@ if (isMainModule) { } // `lousho studio` mints the token itself and prints this URL; only print it when launched directly. if (!process.env[STUDIO_TOKEN_ENV]) console.log(`[lousho studio] Agent Forge: ${handle.url}`); - const shutdown = () => { - void handle.close().finally(() => process.exit(0)); - }; - process.on('SIGINT', shutdown); - process.on('SIGTERM', shutdown); + shutdownOnSignals(handle); }) .catch((error) => { console.error(error instanceof Error ? error.message : String(error)); diff --git a/apps/agent-forge/server/judge.ts b/apps/agent-forge/server/judge.ts index f65cfb109..8080032dd 100644 --- a/apps/agent-forge/server/judge.ts +++ b/apps/agent-forge/server/judge.ts @@ -25,8 +25,8 @@ * daemon unchanged. */ import type { Express, Request, Response } from 'express'; -import type { AgentSpec } from 'lousho'; -import { evaluatorTraceFrom, type EvaluatorTrace, type ObservationRow } from 'lousho/traces'; +import type { AgentSpec } from '@linuxdevil/lousho'; +import { evaluatorTraceFrom, type EvaluatorTrace, type ObservationRow } from '@linuxdevil/lousho/traces'; import { buildAgentFromSpec } from './buildAgent'; import { daemonHeaders, observeTarget } from './observeProxy'; import { firstJsonObject } from './passes/jsonReply'; diff --git a/apps/agent-forge/server/lifecycle/__tests__/failureStreak.test.ts b/apps/agent-forge/server/lifecycle/__tests__/failureStreak.test.ts index ec20f3b7d..069bab7a1 100644 --- a/apps/agent-forge/server/lifecycle/__tests__/failureStreak.test.ts +++ b/apps/agent-forge/server/lifecycle/__tests__/failureStreak.test.ts @@ -3,7 +3,7 @@ import { EventEmitter } from 'node:events'; import * as fs from 'node:fs'; import * as os from 'node:os'; import * as path from 'node:path'; -import type { AgentSpecV2 } from 'lousho'; +import type { AgentSpecV2 } from '@linuxdevil/lousho'; import type { AgentRunStatusPayload, StudioEvent } from '../../../shared/wireTypes'; import { AgentRepo } from '../../storage/agentRepo'; import { StudioEventBus } from '../../events/eventBus'; diff --git a/apps/agent-forge/server/lifecycle/__tests__/publish.test.ts b/apps/agent-forge/server/lifecycle/__tests__/publish.test.ts index d35f2faaf..25146ff8f 100644 --- a/apps/agent-forge/server/lifecycle/__tests__/publish.test.ts +++ b/apps/agent-forge/server/lifecycle/__tests__/publish.test.ts @@ -2,7 +2,7 @@ import { afterEach, beforeEach, describe, expect, it } from 'vitest'; import * as fs from 'node:fs'; import * as os from 'node:os'; import * as path from 'node:path'; -import type { AgentSpecV2 } from 'lousho'; +import type { AgentSpecV2 } from '@linuxdevil/lousho'; import type { AgentMeta, StudioEvent, VersionListItem } from '../../../shared/wireTypes'; import { StudioEventBus } from '../../events/eventBus'; import { AgentRepo } from '../../storage/agentRepo'; diff --git a/apps/agent-forge/server/lifecycle/copy.ts b/apps/agent-forge/server/lifecycle/copy.ts index e674117ac..5689fbd57 100644 --- a/apps/agent-forge/server/lifecycle/copy.ts +++ b/apps/agent-forge/server/lifecycle/copy.ts @@ -10,7 +10,7 @@ * the new id has no sidecar dir until this write. */ import { randomUUID } from 'node:crypto'; -import type { AgentSpecV2 } from 'lousho'; +import type { AgentSpecV2 } from '@linuxdevil/lousho'; import { isValidAgentId } from '../../shared/agentId'; import type { AgentMeta, AgentRecord, CanvasLayout } from '../../shared/wireTypes'; diff --git a/apps/agent-forge/server/lifecycle/export.ts b/apps/agent-forge/server/lifecycle/export.ts index 235b0e7a1..067903f33 100644 --- a/apps/agent-forge/server/lifecycle/export.ts +++ b/apps/agent-forge/server/lifecycle/export.ts @@ -10,7 +10,7 @@ */ import type { Express, Request, Response } from 'express'; import { stringify as stringifyYaml } from 'yaml'; -import type { AgentSpecV2 } from 'lousho'; +import type { AgentSpecV2 } from '@linuxdevil/lousho'; import type { AgentBundle } from '../../shared/wireTypes'; import { asyncRoute, paramId, sendError } from '../http/routeUtils'; import { daemonHeaders, observeTarget } from '../observeProxy'; diff --git a/apps/agent-forge/server/lifecycle/import.ts b/apps/agent-forge/server/lifecycle/import.ts index 55dcf4b09..2986c9429 100644 --- a/apps/agent-forge/server/lifecycle/import.ts +++ b/apps/agent-forge/server/lifecycle/import.ts @@ -20,7 +20,7 @@ */ import type { Express, Request, Response } from 'express'; import { parse as parseYaml } from 'yaml'; -import { agentSpecV2Schema, upgradeSpec, type AgentSpecV2 } from 'lousho'; +import { agentSpecV2Schema, upgradeSpec, type AgentSpecV2 } from '@linuxdevil/lousho'; import { isValidAgentId } from '../../shared/agentId'; import { LabelError, normalizeLabels } from '../../shared/labels'; import type { AgentBundle, AgentMeta, AgentRecord, ImportMode, ImportPreview, ImportRequest } from '../../shared/wireTypes'; diff --git a/apps/agent-forge/server/lifecycle/publish.ts b/apps/agent-forge/server/lifecycle/publish.ts index bb25c3e6f..8e1f6dcd5 100644 --- a/apps/agent-forge/server/lifecycle/publish.ts +++ b/apps/agent-forge/server/lifecycle/publish.ts @@ -14,7 +14,7 @@ * X05 the `published.yaml` write inside it. */ import type { Express, Request, Response } from 'express'; -import type { AgentSpecV2 } from 'lousho'; +import type { AgentSpecV2 } from '@linuxdevil/lousho'; import type { AgentMeta, PublishState, VersionListItem } from '../../shared/wireTypes'; import type { StudioEventBus } from '../events/eventBus'; import { asyncRoute, paramId, sendError } from '../http/routeUtils'; diff --git a/apps/agent-forge/server/logEntries.ts b/apps/agent-forge/server/logEntries.ts index b207444ea..7eeb14552 100644 --- a/apps/agent-forge/server/logEntries.ts +++ b/apps/agent-forge/server/logEntries.ts @@ -3,8 +3,8 @@ * `LogEntry` rows the Logs tab renders. Used by runRegistry.ts. */ import { randomUUID } from 'node:crypto'; -import type { AgentEvent, AgentEventOf, AgentEventType } from 'lousho'; -import type { FlowExecutionEvent, FlowExecutionEventOf } from 'lousho/flows'; +import type { AgentEvent, AgentEventOf, AgentEventType } from '@linuxdevil/lousho'; +import type { FlowExecutionEvent, FlowExecutionEventOf } from '@linuxdevil/lousho/flows'; import type { LogEntry } from '../shared/wireTypes'; /** The event-specific part of a `LogEntry`; id/agentId/timestamp are added by the translators. */ diff --git a/apps/agent-forge/server/nodeStates.ts b/apps/agent-forge/server/nodeStates.ts index d64a965c4..687c782b7 100644 --- a/apps/agent-forge/server/nodeStates.ts +++ b/apps/agent-forge/server/nodeStates.ts @@ -8,7 +8,7 @@ * guessing from span names. Frames carry node ids and state only - * never secret values. */ -import { APPROVAL_WAIT_SPAN_NAME, SdkAttr, type Span } from 'lousho'; +import { APPROVAL_WAIT_SPAN_NAME, SdkAttr, type Span } from '@linuxdevil/lousho'; import type { NodeRunState, NodeStatePayload } from '../shared/wireTypes'; /** diff --git a/apps/agent-forge/server/observeReporting.ts b/apps/agent-forge/server/observeReporting.ts index b57821334..ae3766565 100644 --- a/apps/agent-forge/server/observeReporting.ts +++ b/apps/agent-forge/server/observeReporting.ts @@ -17,7 +17,7 @@ import { type ObserveExporterOptions, type Span, type TraceExporter, -} from 'lousho/traces'; +} from '@linuxdevil/lousho/traces'; import { observeTarget } from './observeProxy'; import type { CollectorSource } from './collector'; diff --git a/apps/agent-forge/server/passes/__tests__/draftReview.test.ts b/apps/agent-forge/server/passes/__tests__/draftReview.test.ts index d475501d2..cd0980f2c 100644 --- a/apps/agent-forge/server/passes/__tests__/draftReview.test.ts +++ b/apps/agent-forge/server/passes/__tests__/draftReview.test.ts @@ -2,7 +2,7 @@ import { describe, it, expect, beforeEach, afterEach, vi, type Mock } from 'vite import * as fs from 'node:fs'; import * as os from 'node:os'; import * as path from 'node:path'; -import type { AgentSpecV2 } from 'lousho'; +import type { AgentSpecV2 } from '@linuxdevil/lousho'; import type { SpecChange } from '../../../shared/wireTypes'; import { AgentRepo } from '../../storage/agentRepo'; import { DraftStore, type NewDraftInput } from '../../storage/draftStore'; diff --git a/apps/agent-forge/server/passes/__tests__/passModel.test.ts b/apps/agent-forge/server/passes/__tests__/passModel.test.ts index 0448ca421..eb451f942 100644 --- a/apps/agent-forge/server/passes/__tests__/passModel.test.ts +++ b/apps/agent-forge/server/passes/__tests__/passModel.test.ts @@ -3,8 +3,8 @@ import * as fs from 'node:fs'; import * as os from 'node:os'; import * as path from 'node:path'; import { z } from 'zod'; -import { ConfigurationError } from 'lousho'; -import type { GenerateOptions, GenerateResult } from 'lousho'; +import { ConfigurationError } from '@linuxdevil/lousho'; +import type { GenerateOptions, GenerateResult } from '@linuxdevil/lousho'; import { callPassModel } from '../passModel'; import { SettingsStore } from '../../settingsStore'; import { SecretsStore } from '../../secretsStore'; diff --git a/apps/agent-forge/server/passes/__tests__/passRunner.test.ts b/apps/agent-forge/server/passes/__tests__/passRunner.test.ts index 7553e4c7a..b133139e2 100644 --- a/apps/agent-forge/server/passes/__tests__/passRunner.test.ts +++ b/apps/agent-forge/server/passes/__tests__/passRunner.test.ts @@ -2,7 +2,7 @@ import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest'; import * as fs from 'node:fs'; import * as os from 'node:os'; import * as path from 'node:path'; -import type { Span, TraceExporter } from 'lousho/traces'; +import type { Span, TraceExporter } from '@linuxdevil/lousho/traces'; import { PassOutputError, PassRefusedError, diff --git a/apps/agent-forge/server/passes/__tests__/portability.test.ts b/apps/agent-forge/server/passes/__tests__/portability.test.ts index 0ab76caeb..c118af8fd 100644 --- a/apps/agent-forge/server/passes/__tests__/portability.test.ts +++ b/apps/agent-forge/server/passes/__tests__/portability.test.ts @@ -8,7 +8,7 @@ import { describe, it, expect, beforeEach, afterEach, vi, type Mock } from 'vite import * as fs from 'node:fs'; import * as os from 'node:os'; import * as path from 'node:path'; -import type { AgentSpecV2 } from 'lousho'; +import type { AgentSpecV2 } from '@linuxdevil/lousho'; import { AgentRepo } from '../../storage/agentRepo'; import { SuggestionStore } from '../../storage/suggestionStore'; import { specHash } from '../../storage/specHash'; diff --git a/apps/agent-forge/server/passes/__tests__/promptSuggestion.digest.test.ts b/apps/agent-forge/server/passes/__tests__/promptSuggestion.digest.test.ts index 892b588a5..1967bf5de 100644 --- a/apps/agent-forge/server/passes/__tests__/promptSuggestion.digest.test.ts +++ b/apps/agent-forge/server/passes/__tests__/promptSuggestion.digest.test.ts @@ -1,5 +1,5 @@ import { describe, it, expect } from 'vitest'; -import type { ObservationRow } from 'lousho/traces'; +import type { ObservationRow } from '@linuxdevil/lousho/traces'; import { buildFrictionDigest } from '../promptSuggestion'; /** A minimal ObservationRow; every field the digest does not read defaults sanely. */ diff --git a/apps/agent-forge/server/passes/__tests__/promptSuggestion.test.ts b/apps/agent-forge/server/passes/__tests__/promptSuggestion.test.ts index 026d8c901..680879793 100644 --- a/apps/agent-forge/server/passes/__tests__/promptSuggestion.test.ts +++ b/apps/agent-forge/server/passes/__tests__/promptSuggestion.test.ts @@ -2,8 +2,8 @@ import { describe, it, expect, beforeEach, afterEach, vi, type Mock } from 'vite import * as fs from 'node:fs'; import * as os from 'node:os'; import * as path from 'node:path'; -import type { AgentSpecV2 } from 'lousho'; -import type { ObservationRow } from 'lousho/traces'; +import type { AgentSpecV2 } from '@linuxdevil/lousho'; +import type { ObservationRow } from '@linuxdevil/lousho/traces'; import type { PromptSuggestion } from '../../../shared/wireTypes'; import { AgentRepo } from '../../storage/agentRepo'; import { VersionStore } from '../../storage/versionStore'; diff --git a/apps/agent-forge/server/passes/__tests__/versionSummary.test.ts b/apps/agent-forge/server/passes/__tests__/versionSummary.test.ts index 37f673bed..f8c9411b6 100644 --- a/apps/agent-forge/server/passes/__tests__/versionSummary.test.ts +++ b/apps/agent-forge/server/passes/__tests__/versionSummary.test.ts @@ -2,7 +2,7 @@ import { describe, it, expect, beforeEach, afterEach, vi, type Mock } from 'vite import * as fs from 'node:fs'; import * as os from 'node:os'; import * as path from 'node:path'; -import type { AgentSpecV2 } from 'lousho'; +import type { AgentSpecV2 } from '@linuxdevil/lousho'; import type { SpecSnapshot, VersionKind, VersionListItem, VersionRecord } from '../../../shared/wireTypes'; import { AgentRepo } from '../../storage/agentRepo'; import { VersionStore } from '../../storage/versionStore'; diff --git a/apps/agent-forge/server/passes/draftReview.ts b/apps/agent-forge/server/passes/draftReview.ts index e05bdf16f..ef4c8a90f 100644 --- a/apps/agent-forge/server/passes/draftReview.ts +++ b/apps/agent-forge/server/passes/draftReview.ts @@ -20,7 +20,7 @@ * only the pass kind, key and outcome reach the runner's own records. */ import { z } from 'zod'; -import type { AgentSpecV2 } from 'lousho'; +import type { AgentSpecV2 } from '@linuxdevil/lousho'; import { nodeIdList, toolKey } from '../../shared/nodeIds'; import type { ChangeDraft, diff --git a/apps/agent-forge/server/passes/passModel.ts b/apps/agent-forge/server/passes/passModel.ts index 742b2fe6f..5ac168081 100644 --- a/apps/agent-forge/server/passes/passModel.ts +++ b/apps/agent-forge/server/passes/passModel.ts @@ -17,8 +17,8 @@ * * Prompts and replies are never logged - they hold user instructions. */ -import { estimateCost, SDKError } from 'lousho'; -import type { LLMProvider } from 'lousho'; +import { estimateCost, SDKError } from '@linuxdevil/lousho'; +import type { LLMProvider } from '@linuxdevil/lousho'; import { z } from 'zod'; import type { PassKind } from '../../shared/wireTypes'; import { buildAgentFromSpec } from '../buildAgent'; diff --git a/apps/agent-forge/server/passes/passRunner.ts b/apps/agent-forge/server/passes/passRunner.ts index 0692f1a29..9a6276496 100644 --- a/apps/agent-forge/server/passes/passRunner.ts +++ b/apps/agent-forge/server/passes/passRunner.ts @@ -18,8 +18,8 @@ * (`callPassModel` in L06b throws them for a missing key, a provider * refusal, or a disabled kind). */ -import { recordSpanError, withSpan } from 'lousho'; -import type { TraceExporter } from 'lousho/traces'; +import { recordSpanError, withSpan } from '@linuxdevil/lousho'; +import type { TraceExporter } from '@linuxdevil/lousho/traces'; import type { PassKind } from '../../shared/wireTypes'; import { observeReporting } from '../observeReporting'; import { studioPaths } from '../workspace/studioPaths'; diff --git a/apps/agent-forge/server/passes/portability.ts b/apps/agent-forge/server/passes/portability.ts index 188c2fda0..a057f2a4c 100644 --- a/apps/agent-forge/server/passes/portability.ts +++ b/apps/agent-forge/server/passes/portability.ts @@ -17,7 +17,7 @@ * only the pass kind, key and outcome reach the runner's own records. */ import { z } from 'zod'; -import type { AgentSpecV2 } from 'lousho'; +import type { AgentSpecV2 } from '@linuxdevil/lousho'; import type { PassOutcome, PassRunner } from './passRunner'; import type { SecretsStore } from '../secretsStore'; import type { SettingsStore } from '../settingsStore'; diff --git a/apps/agent-forge/server/passes/portabilityRoutes.ts b/apps/agent-forge/server/passes/portabilityRoutes.ts index d7136c4cc..a0f026a43 100644 --- a/apps/agent-forge/server/passes/portabilityRoutes.ts +++ b/apps/agent-forge/server/passes/portabilityRoutes.ts @@ -12,7 +12,7 @@ * Studio-global secrets/settings stores from the registration deps. */ import type { Express, Request, Response } from 'express'; -import type { AgentSpecV2 } from 'lousho'; +import type { AgentSpecV2 } from '@linuxdevil/lousho'; import { asyncRoute, paramId, sendError } from '../http/routeUtils'; import { ImportError, parseImportText } from '../lifecycle/import'; import type { SecretsStore } from '../secretsStore'; diff --git a/apps/agent-forge/server/passes/promptSuggestion.ts b/apps/agent-forge/server/passes/promptSuggestion.ts index d2fa54b83ebd3366cd586ef5c313dffe48961068..9a7fd73607ce01a539f18a9c11569eb7112f1b1b 100644 GIT binary patch delta 44 pcmbQYm2ulv#tr|NcpP#v^GYjHQp+-P^e6vg(nAs0tixOs4gj2Q5nBKN delta 18 acmdnCm2uuy#tr|NCUY_CZFXTU3I_m7-3I~y diff --git a/apps/agent-forge/server/passes/versionSummary.ts b/apps/agent-forge/server/passes/versionSummary.ts index 9ffac51aa..add937d7a 100644 --- a/apps/agent-forge/server/passes/versionSummary.ts +++ b/apps/agent-forge/server/passes/versionSummary.ts @@ -12,7 +12,7 @@ */ import { diffLines, type Change } from 'diff'; import { z } from 'zod'; -import type { AgentSpecV2 } from 'lousho'; +import type { AgentSpecV2 } from '@linuxdevil/lousho'; import { fieldLabel } from '../../shared/specFieldLabels'; import { nodeIdList, skillKey, toolKey } from '../../shared/nodeIds'; import type { SpecFieldKey } from '../../shared/specFields'; diff --git a/apps/agent-forge/server/pins.ts b/apps/agent-forge/server/pins.ts index 7731ed1a2..c98003625 100644 --- a/apps/agent-forge/server/pins.ts +++ b/apps/agent-forge/server/pins.ts @@ -11,7 +11,7 @@ * approve). The hook matches the FIRST pin whose `toolName` equals the * call's and whose `args` (when set) stable-stringify-equal the call's. */ -import type { AgentHook, ToolCallHookContext } from 'lousho'; +import type { AgentHook, ToolCallHookContext } from '@linuxdevil/lousho'; import { stableStringify } from '../shared/stableJson'; import type { ToolPin } from '../shared/wireTypes'; diff --git a/apps/agent-forge/server/playground.ts b/apps/agent-forge/server/playground.ts index cc5fd2c2b..c5545ea6a 100644 --- a/apps/agent-forge/server/playground.ts +++ b/apps/agent-forge/server/playground.ts @@ -15,15 +15,15 @@ * messages, so the server does not fetch the prompt again. */ import type { Express, Response } from 'express'; -import { AgentExecutor } from 'lousho/executor'; +import { AgentExecutor } from '@linuxdevil/lousho/executor'; import { InMemoryApprovalStore, SDKError, type AgentEvent, type AgentSpec, type Message, -} from 'lousho'; -import { fanOutExporters, type ObserveExporterOptions, type Span, type TraceExporter } from 'lousho/traces'; +} from '@linuxdevil/lousho'; +import { fanOutExporters, type ObserveExporterOptions, type Span, type TraceExporter } from '@linuxdevil/lousho/traces'; import { buildAgentFromSpec } from './buildAgent'; import { observeReporting, type ObserveReporting } from './observeReporting'; import type { SecretsStore } from './secretsStore'; diff --git a/apps/agent-forge/server/routes/__tests__/agentListItems.test.ts b/apps/agent-forge/server/routes/__tests__/agentListItems.test.ts index 80eeb7269..f4fa466ae 100644 --- a/apps/agent-forge/server/routes/__tests__/agentListItems.test.ts +++ b/apps/agent-forge/server/routes/__tests__/agentListItems.test.ts @@ -2,7 +2,7 @@ import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest'; import * as fs from 'node:fs'; import * as os from 'node:os'; import * as path from 'node:path'; -import type { AgentSpecV2 } from 'lousho'; +import type { AgentSpecV2 } from '@linuxdevil/lousho'; import { AgentRepo } from '../../storage/agentRepo'; import { buildAgentListItems } from '../agentListItems'; import type { AgentRunStatusPayload } from '../../../shared/wireTypes'; diff --git a/apps/agent-forge/server/routes/agentListItems.ts b/apps/agent-forge/server/routes/agentListItems.ts index 284c5638b..3583e7982 100644 --- a/apps/agent-forge/server/routes/agentListItems.ts +++ b/apps/agent-forge/server/routes/agentListItems.ts @@ -7,7 +7,7 @@ * bare `new AgentRepo(root)` and no RunManager - `runStatus` is simply * absent there, so `lastRun` falls back to the newest persisted trace. */ -import type { AgentSpecV2 } from 'lousho'; +import type { AgentSpecV2 } from '@linuxdevil/lousho'; import type { AgentLastRun, AgentListItem, AgentRunStatusPayload } from '../../shared/wireTypes'; import type { AgentRepo } from '../storage/agentRepo'; import { listAgentTraces } from '../traceStore'; diff --git a/apps/agent-forge/server/routes/agentRoutes.ts b/apps/agent-forge/server/routes/agentRoutes.ts index 884d72002..b0e85a0fd 100644 --- a/apps/agent-forge/server/routes/agentRoutes.ts +++ b/apps/agent-forge/server/routes/agentRoutes.ts @@ -33,7 +33,7 @@ * `?ws=all` route reuses them verbatim (W06a). */ import type { Express, Response } from 'express'; -import type { AgentSpec, AgentSpecV2 } from 'lousho'; +import type { AgentSpec, AgentSpecV2 } from '@linuxdevil/lousho'; import { LabelError, MAX_LABEL_LENGTH, MAX_LABELS, normalizeLabels } from '../../shared/labels'; import type { AgentMeta, diff --git a/apps/agent-forge/server/routes/catalogRoutes.ts b/apps/agent-forge/server/routes/catalogRoutes.ts index 259c039bc..297c4df54 100644 --- a/apps/agent-forge/server/routes/catalogRoutes.ts +++ b/apps/agent-forge/server/routes/catalogRoutes.ts @@ -17,13 +17,13 @@ * * Tools, guardrails, trigger types and the parsed registry index are * memoised per `registerCatalogRoutes` call (static until the next - * `lousho` upgrade); models are computed per request because stored keys + * `@linuxdevil/lousho` upgrade); models are computed per request because stored keys * change at runtime. `/catalog/mcp/probe` is a later ticket. */ import * as fs from 'node:fs'; import * as path from 'node:path'; import type { Express } from 'express'; -import { listBuiltinTools, listGuardrails, listTriggerTypes } from 'lousho'; +import { listBuiltinTools, listGuardrails, listTriggerTypes } from '@linuxdevil/lousho'; import type { CatalogGuardrail, CatalogKit, diff --git a/apps/agent-forge/server/routes/draftRoutes.ts b/apps/agent-forge/server/routes/draftRoutes.ts index 45504a260..98ae52aab 100644 --- a/apps/agent-forge/server/routes/draftRoutes.ts +++ b/apps/agent-forge/server/routes/draftRoutes.ts @@ -15,7 +15,7 @@ * the stores themselves stay lock-free. */ import type { Express, Response } from 'express'; -import { agentSpecV2Schema } from 'lousho'; +import { agentSpecV2Schema } from '@linuxdevil/lousho'; import { computeChangedFields, SPEC_FIELD_ORDER } from '../../shared/specFields'; import { LabelError, normalizeLabels } from '../../shared/labels'; import type { diff --git a/apps/agent-forge/server/routes/mcpProbe.ts b/apps/agent-forge/server/routes/mcpProbe.ts index e33e4a417..e8ecfee05 100644 --- a/apps/agent-forge/server/routes/mcpProbe.ts +++ b/apps/agent-forge/server/routes/mcpProbe.ts @@ -23,8 +23,8 @@ * spawns a command on this machine; the token gate is the guard). */ import type { Express } from 'express'; -import { agentSpecV2Schema, resolveSecretRef, type McpServerSpec } from 'lousho'; -import { connectMcp, type McpConnections } from 'lousho/mcp'; +import { agentSpecV2Schema, resolveSecretRef, type McpServerSpec } from '@linuxdevil/lousho'; +import { connectMcp, type McpConnections } from '@linuxdevil/lousho/mcp'; import type { McpProbeResponse, McpProbeTool, McpServerSpecV2 } from '../../shared/wireTypes'; import { asyncRoute, sendError } from '../http/routeUtils'; diff --git a/apps/agent-forge/server/routes/versionRoutes.ts b/apps/agent-forge/server/routes/versionRoutes.ts index a0579880e..fee151d82 100644 --- a/apps/agent-forge/server/routes/versionRoutes.ts +++ b/apps/agent-forge/server/routes/versionRoutes.ts @@ -15,7 +15,7 @@ * deps to close over. */ import type { Express, Response } from 'express'; -import { agentSpecV2Schema } from 'lousho'; +import { agentSpecV2Schema } from '@linuxdevil/lousho'; import { diffSpecs } from '../diff/specChange'; import { asyncRoute, paramId, sendError } from '../http/routeUtils'; import { withAgentLock } from '../storage/atomic'; diff --git a/apps/agent-forge/server/runRegistry.ts b/apps/agent-forge/server/runRegistry.ts index bd48781eb..f83b13fb2 100644 --- a/apps/agent-forge/server/runRegistry.ts +++ b/apps/agent-forge/server/runRegistry.ts @@ -15,10 +15,10 @@ */ import { EventEmitter } from 'node:events'; import { randomUUID } from 'node:crypto'; -import { fanOutExporters, fileTraceExporter, type ObserveExporterOptions } from 'lousho/traces'; +import { fanOutExporters, fileTraceExporter, type ObserveExporterOptions } from '@linuxdevil/lousho/traces'; import { agentTraceDir } from './traceStore'; import { observeReporting, withoutCapturedContent, type ObserveReporting } from './observeReporting'; -import { AgentExecutor } from 'lousho/executor'; +import { AgentExecutor } from '@linuxdevil/lousho/executor'; import { type AgentEvent, type AgentInput, @@ -35,8 +35,8 @@ import { type CheckpointHistoryEntry, type ForkPatch, type TrajectoryComparison, -} from 'lousho'; -import { FlowExecutor, type AgentFlow } from 'lousho/flows'; +} from '@linuxdevil/lousho'; +import { FlowExecutor, type AgentFlow } from '@linuxdevil/lousho/flows'; import { buildAgentFromSpec, extractFlowFromSpec } from './buildAgent'; import { toLegacySpec } from './storage/legacySpec'; import { HostRunNotActiveError, type AgentHost, type HostEvent, type HostRunResult } from './host/agentHost'; @@ -883,7 +883,7 @@ export class RunManager extends EventEmitter { agentId: string, flow: AgentFlow, resolvedSpec: AgentSpecV2, - legacySpec: import('lousho').AgentSpec, + legacySpec: import('@linuxdevil/lousho').AgentSpec, existing: RunEntry | undefined, options: { runInput?: string; source?: 'manual' | 'trigger'; triggerId?: string } ): Promise { diff --git a/apps/agent-forge/server/sdkRoot.ts b/apps/agent-forge/server/sdkRoot.ts index 770f0fe37..1fb051ab8 100644 --- a/apps/agent-forge/server/sdkRoot.ts +++ b/apps/agent-forge/server/sdkRoot.ts @@ -1,19 +1,48 @@ /** * The lousho SDK package root - where package-shipped assets like - * `registry/dist/index.json` live, for the catalog routes - * (server/routes/catalogRoutes.ts). + * `registry/dist/index.json` and `docs/` live, for the catalog routes + * (server/routes/catalogRoutes.ts) and the docs tools. * - * Resolution: `LOUSHO_SDK_ROOT` when set (tests, unusual layouts), else - * three levels above this file's own directory. Both places this code runs - * from sit at `apps/agent-forge/` - `server/` under tsx and the bundled - * `dist-server/index.cjs` in prod - so `../../..` lands on the repo root in - * this monorepo and on `node_modules/lousho` (the package root) - * in an installed deployment, never assuming it runs from `server/`. + * Resolution: `LOUSHO_SDK_ROOT` when set (tests, unusual layouts), else the + * installed `@linuxdevil/lousho` package itself - `createRequire` resolves + * its entry and we walk up to the first folder whose `package.json` is + * named `@linuxdevil/lousho`. That works from the monorepo (the workspace + * symlink lands on the repo root) and from `node_modules/@linuxdevil/studio` + * (the SDK is a real dependency there), where the old "three levels up" + * rule broke. */ +import * as fs from 'node:fs'; import * as path from 'node:path'; +import { createRequire } from 'node:module'; import { fileURLToPath } from 'node:url'; -/** The SDK package root: env LOUSHO_SDK_ROOT, else three levels above this file's dir (server/ and dist-server/ are both at apps/agent-forge/). */ -export function sdkRootDir(): string { - return process.env.LOUSHO_SDK_ROOT ?? path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..', '..', '..'); +const SDK_PACKAGE = '@linuxdevil/lousho'; + +/** The SDK package root: env LOUSHO_SDK_ROOT, else the resolved `@linuxdevil/lousho` install. */ +export function sdkRootDir(moduleDir?: string): string { + const env = process.env.LOUSHO_SDK_ROOT; + if (env) return env; + const from = moduleDir ?? path.dirname(fileURLToPath(import.meta.url)); + const require = createRequire(path.join(from, 'noop.cjs')); + let entry: string; + try { + entry = require.resolve(SDK_PACKAGE); + } catch { + throw new Error(`lousho-studio: could not locate the ${SDK_PACKAGE} package. Set LOUSHO_SDK_ROOT.`); + } + let dir = path.dirname(entry); + for (;;) { + const pkgPath = path.join(dir, 'package.json'); + if (fs.existsSync(pkgPath)) { + try { + if ((JSON.parse(fs.readFileSync(pkgPath, 'utf8')) as { name?: string }).name === SDK_PACKAGE) return dir; + } catch { + // Unreadable package.json - keep walking up. + } + } + const parent = path.dirname(dir); + if (parent === dir) break; + dir = parent; + } + throw new Error(`lousho-studio: could not locate the ${SDK_PACKAGE} package. Set LOUSHO_SDK_ROOT.`); } diff --git a/apps/agent-forge/server/storage/__tests__/agentIds.test.ts b/apps/agent-forge/server/storage/__tests__/agentIds.test.ts index 335894064..da73c6fc2 100644 --- a/apps/agent-forge/server/storage/__tests__/agentIds.test.ts +++ b/apps/agent-forge/server/storage/__tests__/agentIds.test.ts @@ -8,7 +8,7 @@ import { describe, it, expect, beforeEach, afterEach } from 'vitest'; import * as fs from 'node:fs'; import * as os from 'node:os'; import * as path from 'node:path'; -import type { AgentSpecV2 } from 'lousho'; +import type { AgentSpecV2 } from '@linuxdevil/lousho'; import { isValidAgentId } from '../../../shared/agentId'; import { AgentRepo } from '../agentRepo'; import { slugAgentId, uniqueAgentId } from '../agentIds'; diff --git a/apps/agent-forge/server/storage/__tests__/agentRepo.test.ts b/apps/agent-forge/server/storage/__tests__/agentRepo.test.ts index 5d484d043..0d6ba2643 100644 --- a/apps/agent-forge/server/storage/__tests__/agentRepo.test.ts +++ b/apps/agent-forge/server/storage/__tests__/agentRepo.test.ts @@ -2,7 +2,7 @@ import { describe, it, expect, beforeEach, afterEach } from 'vitest'; import * as fs from 'node:fs'; import * as os from 'node:os'; import * as path from 'node:path'; -import type { AgentSpecV2 } from 'lousho'; +import type { AgentSpecV2 } from '@linuxdevil/lousho'; import { AgentRepo, InvalidSpecError } from '../agentRepo'; import { specHash } from '../specHash'; import type { AgentMeta, CanvasLayout } from '../../../shared/wireTypes'; diff --git a/apps/agent-forge/server/storage/__tests__/legacySpec.test.ts b/apps/agent-forge/server/storage/__tests__/legacySpec.test.ts index 1a3d5f6ed..f12111275 100644 --- a/apps/agent-forge/server/storage/__tests__/legacySpec.test.ts +++ b/apps/agent-forge/server/storage/__tests__/legacySpec.test.ts @@ -1,5 +1,5 @@ import { describe, it, expect } from 'vitest'; -import { upgradeSpec, type AgentSpec, type AgentSpecV2 } from 'lousho'; +import { upgradeSpec, type AgentSpec, type AgentSpecV2 } from '@linuxdevil/lousho'; import { mergeLegacySave, toLegacySpec } from '../legacySpec'; const base: AgentSpec = { name: 'bot', prompt: 'Be helpful.', provider: { type: 'mock', model: 'm1' } }; diff --git a/apps/agent-forge/server/storage/__tests__/specFields.test.ts b/apps/agent-forge/server/storage/__tests__/specFields.test.ts index d1508c947..fd05980d9 100644 --- a/apps/agent-forge/server/storage/__tests__/specFields.test.ts +++ b/apps/agent-forge/server/storage/__tests__/specFields.test.ts @@ -1,5 +1,5 @@ import { describe, it, expect } from 'vitest'; -import type { AgentSpecV2 } from 'lousho'; +import type { AgentSpecV2 } from '@linuxdevil/lousho'; import { computeChangedFields } from '../../../shared/specFields'; import type { SpecSnapshot } from '../../../shared/wireTypes'; diff --git a/apps/agent-forge/server/storage/__tests__/specHash.test.ts b/apps/agent-forge/server/storage/__tests__/specHash.test.ts index 8d5b7cada..844744691 100644 --- a/apps/agent-forge/server/storage/__tests__/specHash.test.ts +++ b/apps/agent-forge/server/storage/__tests__/specHash.test.ts @@ -1,5 +1,5 @@ import { describe, it, expect } from 'vitest'; -import type { AgentSpecV2 } from 'lousho'; +import type { AgentSpecV2 } from '@linuxdevil/lousho'; import { sortKeysDeep } from '../../../shared/stableJson'; import { specHash } from '../specHash'; diff --git a/apps/agent-forge/server/storage/__tests__/versionStore.summary.test.ts b/apps/agent-forge/server/storage/__tests__/versionStore.summary.test.ts index d1643d07e..dd3fb0cce 100644 --- a/apps/agent-forge/server/storage/__tests__/versionStore.summary.test.ts +++ b/apps/agent-forge/server/storage/__tests__/versionStore.summary.test.ts @@ -3,7 +3,7 @@ import * as fs from 'node:fs'; import * as os from 'node:os'; import * as path from 'node:path'; import { stringify as stringifyYaml } from 'yaml'; -import type { AgentSpecV2 } from 'lousho'; +import type { AgentSpecV2 } from '@linuxdevil/lousho'; import { AgentRepo } from '../agentRepo'; import { VersionStore, type CaptureInput } from '../versionStore'; import type { VersionListItem, VersionRecord } from '../../../shared/wireTypes'; diff --git a/apps/agent-forge/server/storage/__tests__/versionStore.test.ts b/apps/agent-forge/server/storage/__tests__/versionStore.test.ts index 2eac7c2d7..4d0d841af 100644 --- a/apps/agent-forge/server/storage/__tests__/versionStore.test.ts +++ b/apps/agent-forge/server/storage/__tests__/versionStore.test.ts @@ -3,7 +3,7 @@ import * as fs from 'node:fs'; import * as os from 'node:os'; import * as path from 'node:path'; import { stringify as stringifyYaml } from 'yaml'; -import type { AgentSpecV2 } from 'lousho'; +import type { AgentSpecV2 } from '@linuxdevil/lousho'; import { AgentRepo } from '../agentRepo'; import { VersionStore, type CaptureInput } from '../versionStore'; import type { VersionListItem, VersionRecord } from '../../../shared/wireTypes'; diff --git a/apps/agent-forge/server/storage/agentRepo.ts b/apps/agent-forge/server/storage/agentRepo.ts index 88184283e..4f4043363 100644 --- a/apps/agent-forge/server/storage/agentRepo.ts +++ b/apps/agent-forge/server/storage/agentRepo.ts @@ -21,7 +21,7 @@ import * as fs from 'node:fs'; import { promises as fsPromises } from 'node:fs'; import * as path from 'node:path'; import { parse as parseYaml, stringify as stringifyYaml } from 'yaml'; -import { agentSpecV2Schema, loadSpecV2, upgradeSpec, type AgentSpecV2 } from 'lousho'; +import { agentSpecV2Schema, loadSpecV2, upgradeSpec, type AgentSpecV2 } from '@linuxdevil/lousho'; import { isValidAgentId } from '../../shared/agentId'; import type { AgentMeta, CanvasLayout } from '../../shared/wireTypes'; import { readJsonFile, writeFileAtomic, writeJsonAtomic } from './atomic'; diff --git a/apps/agent-forge/server/storage/draftStore.ts b/apps/agent-forge/server/storage/draftStore.ts index 0c5e1893c..16d5eea1e 100644 --- a/apps/agent-forge/server/storage/draftStore.ts +++ b/apps/agent-forge/server/storage/draftStore.ts @@ -18,7 +18,7 @@ import { promises as fsPromises } from 'node:fs'; import * as path from 'node:path'; import { randomUUID } from 'node:crypto'; -import type { AgentSpecV2 } from 'lousho'; +import type { AgentSpecV2 } from '@linuxdevil/lousho'; import type { ChangeDraft, DraftFieldClasses, diff --git a/apps/agent-forge/server/storage/legacySpec.ts b/apps/agent-forge/server/storage/legacySpec.ts index 3e52d03b7..3bb4ce622 100644 --- a/apps/agent-forge/server/storage/legacySpec.ts +++ b/apps/agent-forge/server/storage/legacySpec.ts @@ -12,7 +12,7 @@ * Deleted once the routes (D06a), RunManager (D12b) and the canvas (C02) * speak spec v2 directly. */ -import { agentSpecV2Schema, upgradeSpec, type AgentSpec, type AgentSpecGuardrail, type AgentSpecPolicy, type AgentSpecV2 } from 'lousho'; +import { agentSpecV2Schema, upgradeSpec, type AgentSpec, type AgentSpecGuardrail, type AgentSpecPolicy, type AgentSpecV2 } from '@linuxdevil/lousho'; import type { SerializedHook } from '../compileHooks'; import { sortKeysDeep } from '../../shared/stableJson'; diff --git a/apps/agent-forge/server/storage/specHash.ts b/apps/agent-forge/server/storage/specHash.ts index 66a1651e7..a79f98fdf 100644 --- a/apps/agent-forge/server/storage/specHash.ts +++ b/apps/agent-forge/server/storage/specHash.ts @@ -10,7 +10,7 @@ */ import { createHash } from 'node:crypto'; import { stringify as stringifyYaml } from 'yaml'; -import type { AgentSpecV2 } from 'lousho'; +import type { AgentSpecV2 } from '@linuxdevil/lousho'; import { sortKeysDeep } from '../../shared/stableJson'; /** The canonical YAML a spec hashes as: `yaml.stringify` of the deep key-sorted spec. */ diff --git a/apps/agent-forge/server/templates/templateCatalog.ts b/apps/agent-forge/server/templates/templateCatalog.ts index d5636f6cb..c4a56cba9 100644 --- a/apps/agent-forge/server/templates/templateCatalog.ts +++ b/apps/agent-forge/server/templates/templateCatalog.ts @@ -22,7 +22,7 @@ import * as fs from 'node:fs'; import * as path from 'node:path'; import type { Express } from 'express'; -import { loadSpecV2, type AgentSpecV2 } from 'lousho'; +import { loadSpecV2, type AgentSpecV2 } from '@linuxdevil/lousho'; import type { RequirementKind, TemplateDetail, TemplateSummary } from '../../shared/wireTypes'; import { asyncRoute, paramId, sendError } from '../http/routeUtils'; import { sdkRootDir } from '../sdkRoot'; diff --git a/apps/agent-forge/server/traceStore.ts b/apps/agent-forge/server/traceStore.ts index 56f3ca964..9143b2702 100644 --- a/apps/agent-forge/server/traceStore.ts +++ b/apps/agent-forge/server/traceStore.ts @@ -10,7 +10,7 @@ */ import * as fs from 'node:fs'; import * as path from 'node:path'; -import { listTraces, readTrace, type TraceSummary } from 'lousho/traces'; +import { listTraces, readTrace, type TraceSummary } from '@linuxdevil/lousho/traces'; import type { SpanEvent, TraceSummaryPayload } from '../shared/wireTypes'; /** A trace id is a UUID in practice; this is the strictest shape that still allows prefixes. */ diff --git a/apps/agent-forge/server/triggers/abortListeners.ts b/apps/agent-forge/server/triggers/abortListeners.ts index 2f22eece8..1c5ff346b 100644 --- a/apps/agent-forge/server/triggers/abortListeners.ts +++ b/apps/agent-forge/server/triggers/abortListeners.ts @@ -16,8 +16,8 @@ * never blocks the dispatch. Event bodies are never logged (they may * carry secrets); only listener ids and expressions are. */ -import type { AgentSpecV2 } from 'lousho'; -import { evaluateSafeExpression, ExpressionError } from 'lousho/flows'; +import type { AgentSpecV2 } from '@linuxdevil/lousho'; +import { evaluateSafeExpression, ExpressionError } from '@linuxdevil/lousho/flows'; import type { HostRun, TriggerHost } from './triggerHost'; diff --git a/apps/agent-forge/server/triggers/chainTriggers.ts b/apps/agent-forge/server/triggers/chainTriggers.ts index 8395f6eaa..3d21e9bb0 100644 --- a/apps/agent-forge/server/triggers/chainTriggers.ts +++ b/apps/agent-forge/server/triggers/chainTriggers.ts @@ -22,8 +22,8 @@ * A cancelled run (`RunEnd.cancelled`, or a manual `stopped`/abort) * chains nothing, and `paused` is not an end. */ -import type { AgentSpecV2 } from 'lousho'; -import type { TriggerSpec } from 'lousho/spec'; +import type { AgentSpecV2 } from '@linuxdevil/lousho'; +import type { TriggerSpec } from '@linuxdevil/lousho/spec'; import type { AgentRepo } from '../storage/agentRepo'; import type { RunManager } from '../runRegistry'; diff --git a/apps/agent-forge/server/triggers/channelTriggers.ts b/apps/agent-forge/server/triggers/channelTriggers.ts index b4461ea63..d1ff9926c 100644 --- a/apps/agent-forge/server/triggers/channelTriggers.ts +++ b/apps/agent-forge/server/triggers/channelTriggers.ts @@ -39,9 +39,9 @@ import { type Channel, type ChannelsHandler, type SimpleAgent, -} from 'lousho'; -import type { SecretRef, TriggerSpec } from 'lousho/spec'; -import { evaluateSafeExpression } from 'lousho/flows'; +} from '@linuxdevil/lousho'; +import type { SecretRef, TriggerSpec } from '@linuxdevil/lousho/spec'; +import { evaluateSafeExpression } from '@linuxdevil/lousho/flows'; import type { ChannelTriggerStatus } from '../../shared/wireTypes'; import type { AgentRepo } from '../storage/agentRepo'; diff --git a/apps/agent-forge/server/triggers/cronTriggers.ts b/apps/agent-forge/server/triggers/cronTriggers.ts index 8b05d8445..f56b27731 100644 --- a/apps/agent-forge/server/triggers/cronTriggers.ts +++ b/apps/agent-forge/server/triggers/cronTriggers.ts @@ -17,8 +17,8 @@ */ import * as path from 'node:path'; -import { CronExpressionError, parseCronExpression, type CronSchedule } from 'lousho/triggers'; -import type { TriggerSpec } from 'lousho/spec'; +import { CronExpressionError, parseCronExpression, type CronSchedule } from '@linuxdevil/lousho/triggers'; +import type { TriggerSpec } from '@linuxdevil/lousho/spec'; import { readJsonFile, withAgentLock, writeJsonAtomic } from '../storage/atomic'; import { AgentRepo } from '../storage/agentRepo'; diff --git a/apps/agent-forge/server/triggers/runtime.ts b/apps/agent-forge/server/triggers/runtime.ts index 3f7b8dec4..26897538e 100644 --- a/apps/agent-forge/server/triggers/runtime.ts +++ b/apps/agent-forge/server/triggers/runtime.ts @@ -8,8 +8,8 @@ */ import { randomUUID } from 'node:crypto'; -import type { SimpleAgent, TraceExporter } from 'lousho'; -import { fanOutExporters, fileTraceExporter } from 'lousho/traces'; +import type { SimpleAgent, TraceExporter } from '@linuxdevil/lousho'; +import { fanOutExporters, fileTraceExporter } from '@linuxdevil/lousho/traces'; import type { LogEntry } from '../../shared/wireTypes'; import { buildRunnableAgent, type StudioRunDeps } from '../buildAgentV2'; diff --git a/apps/agent-forge/server/triggers/triggerHost.ts b/apps/agent-forge/server/triggers/triggerHost.ts index 1e3cea37f..6b2a6a6bc 100644 --- a/apps/agent-forge/server/triggers/triggerHost.ts +++ b/apps/agent-forge/server/triggers/triggerHost.ts @@ -15,8 +15,8 @@ */ import { randomUUID } from 'node:crypto'; -import type { AgentSpecV2 } from 'lousho'; -import type { TriggerSpec } from 'lousho/spec'; +import type { AgentSpecV2 } from '@linuxdevil/lousho'; +import type { TriggerSpec } from '@linuxdevil/lousho/spec'; import type { AgentRepo } from '../storage/agentRepo'; import type { RunManager } from '../runRegistry'; diff --git a/apps/agent-forge/server/triggers/webhookRoute.ts b/apps/agent-forge/server/triggers/webhookRoute.ts index 6ec8ef881..40f379485 100644 --- a/apps/agent-forge/server/triggers/webhookRoute.ts +++ b/apps/agent-forge/server/triggers/webhookRoute.ts @@ -23,10 +23,10 @@ * workspace mode. Secrets are Studio-global (`resolveSecret` dep). */ import type { Express, Request, Response } from 'express'; -import { evaluateSafeExpression } from 'lousho/flows'; -import { resolveSecretRef } from 'lousho'; -import type { TriggerSpec } from 'lousho/spec'; -import { checkWebhookAuth } from 'lousho/triggers'; +import { evaluateSafeExpression } from '@linuxdevil/lousho/flows'; +import { resolveSecretRef } from '@linuxdevil/lousho'; +import type { TriggerSpec } from '@linuxdevil/lousho/spec'; +import { checkWebhookAuth } from '@linuxdevil/lousho/triggers'; import { isLoopbackHost } from '../auth'; import { asyncRoute, sendError } from '../http/routeUtils'; diff --git a/apps/agent-forge/server/tsup.config.ts b/apps/agent-forge/server/tsup.config.ts index fdac6c4f1..c8192044b 100644 --- a/apps/agent-forge/server/tsup.config.ts +++ b/apps/agent-forge/server/tsup.config.ts @@ -39,8 +39,10 @@ import { defineConfig } from 'tsup'; * bundled here instead of left as a real `require`/`import`. */ export default defineConfig({ - entry: { index: 'server/index.ts' }, + entry: { index: 'server/index.ts', cli: 'server/cli.ts' }, outDir: 'dist-server', + // The `lousho-studio` bin. Harmless on index.cjs (a comment line). + banner: { js: '#!/usr/bin/env node' }, // CJS, not ESM: bundling `express` (and its own dependency `debug`, which // does `require('tty')`/`require('util')` conditionally at runtime to // detect a TTY) as ESM makes esbuild emit those as its own @@ -61,6 +63,6 @@ export default defineConfig({ clean: true, treeshake: true, minify: false, - external: ['lousho', 'yaml'], + external: ['@linuxdevil/lousho', 'yaml', 'ai', 'zod', '@ai-sdk/openai', '@ai-sdk/anthropic'], noExternal: ['express', 'ws'], }); diff --git a/apps/agent-forge/server/workspace/__tests__/middleware.test.ts b/apps/agent-forge/server/workspace/__tests__/middleware.test.ts index 001304cb0..9c5f07ba0 100644 --- a/apps/agent-forge/server/workspace/__tests__/middleware.test.ts +++ b/apps/agent-forge/server/workspace/__tests__/middleware.test.ts @@ -1,7 +1,7 @@ import { describe, it, expect } from 'vitest'; import express from 'express'; import request from 'supertest'; -import { TriggerRegistry } from 'lousho/triggers'; +import { TriggerRegistry } from '@linuxdevil/lousho/triggers'; import { ALL_WORKSPACES } from '../../../shared/workspace'; import type { LegacyAgentStore } from '../../../src/persistence/AgentStore'; import type { RunManager } from '../../runRegistry'; diff --git a/apps/agent-forge/server/workspace/__tests__/workspaceManager.test.ts b/apps/agent-forge/server/workspace/__tests__/workspaceManager.test.ts index dd1e407ef..90c16d305 100644 --- a/apps/agent-forge/server/workspace/__tests__/workspaceManager.test.ts +++ b/apps/agent-forge/server/workspace/__tests__/workspaceManager.test.ts @@ -16,7 +16,7 @@ import type { InboxService } from '../../inbox/inboxService'; import type { TriggerHost } from '../../triggers/triggerHost'; import type { CronTriggers } from '../../triggers/cronTriggers'; import type { LegacyAgentStore } from '../../../src/persistence/AgentStore'; -import { TriggerRegistry } from 'lousho/triggers'; +import { TriggerRegistry } from '@linuxdevil/lousho/triggers'; import type { WorkspaceContext } from '../context'; import type { WorkspaceRef } from '../../../shared/workspace'; import { WorkspaceRegistry } from '../workspaceRegistry'; diff --git a/apps/agent-forge/server/workspace/agentDirRoutes.ts b/apps/agent-forge/server/workspace/agentDirRoutes.ts index 573e3b673..82142bcfd 100644 --- a/apps/agent-forge/server/workspace/agentDirRoutes.ts +++ b/apps/agent-forge/server/workspace/agentDirRoutes.ts @@ -12,7 +12,7 @@ */ import { stat } from 'node:fs/promises'; import type { Express, Request, Response } from 'express'; -import { agentDirToSpec, SDKError } from 'lousho'; +import { agentDirToSpec, SDKError } from '@linuxdevil/lousho'; import type { CodeAgentSpecResponse, WorkspaceScanResult } from '../../shared/wireTypes'; import { asyncRoute, sendError } from '../http/routeUtils'; import { workspaceOf } from './middleware'; diff --git a/apps/agent-forge/server/workspace/context.ts b/apps/agent-forge/server/workspace/context.ts index 13ffeb8e8..aa0f45f3a 100644 --- a/apps/agent-forge/server/workspace/context.ts +++ b/apps/agent-forge/server/workspace/context.ts @@ -13,7 +13,7 @@ * Studio was launched in; `WorkspaceResolver` is the seam a lazy, * registry-backed multi-workspace resolver plugs into later. */ -import { TriggerRegistry } from 'lousho/triggers'; +import { TriggerRegistry } from '@linuxdevil/lousho/triggers'; import { ALL_WORKSPACES, type WorkspaceRef } from '../../shared/workspace'; import type { LegacyAgentStore } from '../../src/persistence/AgentStore'; import { createFsAgentStore } from '../../src/persistence/fsAgentStore'; diff --git a/apps/agent-forge/shared/authorWire.ts b/apps/agent-forge/shared/authorWire.ts index a52bd6ec2..db5db5078 100644 --- a/apps/agent-forge/shared/authorWire.ts +++ b/apps/agent-forge/shared/authorWire.ts @@ -9,7 +9,7 @@ * module instead of re-declaring them. */ -import type { AgentSpecV2 } from 'lousho'; +import type { AgentSpecV2 } from '@linuxdevil/lousho'; import type { Requirement, SpecChange } from './wireTypes'; // The readiness row AA13's `computeRequirements` returns and the Author's diff --git a/apps/agent-forge/shared/nodeIds.ts b/apps/agent-forge/shared/nodeIds.ts index 0823dc703..828e99022 100644 --- a/apps/agent-forge/shared/nodeIds.ts +++ b/apps/agent-forge/shared/nodeIds.ts @@ -8,7 +8,7 @@ * `shared/` ships in the browser bundle, so basenames are computed by hand * (`node:path` is not available here). */ -import type { AgentSpecV2 } from 'lousho'; +import type { AgentSpecV2 } from '@linuxdevil/lousho'; /** One `spec.tools` entry (the SDK's ToolRef union, derived here since it is not a public export). */ type ToolRef = NonNullable[number]; diff --git a/apps/agent-forge/shared/specFields.ts b/apps/agent-forge/shared/specFields.ts index b2db0a542..b69c7cb78 100644 --- a/apps/agent-forge/shared/specFields.ts +++ b/apps/agent-forge/shared/specFields.ts @@ -7,7 +7,7 @@ * `diffSpecs` (the `SpecChange[]` the diff route returns), so the Versions * tab's chips and the diff sheet never disagree or reshuffle. */ -import type { AgentSpecV2 } from 'lousho'; +import type { AgentSpecV2 } from '@linuxdevil/lousho'; import { stableStringify } from './stableJson'; import type { SpecSection, SpecSnapshot } from './wireTypes'; diff --git a/apps/agent-forge/shared/wireTypes.ts b/apps/agent-forge/shared/wireTypes.ts index 023f27848..39a0f131b 100644 --- a/apps/agent-forge/shared/wireTypes.ts +++ b/apps/agent-forge/shared/wireTypes.ts @@ -20,7 +20,7 @@ import type { listGuardrails, listModels, listTriggerTypes, -} from 'lousho'; +} from '@linuxdevil/lousho'; import type { WorkspaceRef } from './workspace'; import type { AuthorSettings } from './authorWire'; diff --git a/apps/agent-forge/src/canvas/kinds/flow/FlowInputsEditor.tsx b/apps/agent-forge/src/canvas/kinds/flow/FlowInputsEditor.tsx index 12283787f..f1be0dad1 100644 --- a/apps/agent-forge/src/canvas/kinds/flow/FlowInputsEditor.tsx +++ b/apps/agent-forge/src/canvas/kinds/flow/FlowInputsEditor.tsx @@ -8,7 +8,7 @@ import type { ChangeEvent } from 'react'; import { Plus, X } from 'lucide-react'; import { Field } from '../../../ui/Field'; import { Checkbox, Input, Select } from '../../../ui/controls'; -import type { FlowInputType, FlowInputVariable } from 'lousho/flows'; +import type { FlowInputType, FlowInputVariable } from '@linuxdevil/lousho/flows'; import './flow.css'; const INPUT_TYPES: FlowInputType[] = ['shortText', 'url', 'longText', 'number', 'json', 'fileBase64']; diff --git a/apps/agent-forge/src/canvas/kinds/flow/FlowMetaSection.tsx b/apps/agent-forge/src/canvas/kinds/flow/FlowMetaSection.tsx index b9c055282..6b3fcca0d 100644 --- a/apps/agent-forge/src/canvas/kinds/flow/FlowMetaSection.tsx +++ b/apps/agent-forge/src/canvas/kinds/flow/FlowMetaSection.tsx @@ -9,7 +9,7 @@ import { InspectorSection } from '../../../components/InspectorSection'; import { Field } from '../../../ui/Field'; import { Input, Textarea } from '../../../ui/controls'; import { JsonField } from '../../fields/JsonField'; -import type { AgentFlow, FlowInputVariable } from 'lousho/flows'; +import type { AgentFlow, FlowInputVariable } from '@linuxdevil/lousho/flows'; import type { KindFieldsProps } from '../index'; import { FLOW_CODE_PATTERN } from './flowForm'; import { FlowInputsEditor } from './FlowInputsEditor'; diff --git a/apps/agent-forge/src/canvas/kinds/flow/RunOptionsSection.tsx b/apps/agent-forge/src/canvas/kinds/flow/RunOptionsSection.tsx index 5699cab06..b8c801498 100644 --- a/apps/agent-forge/src/canvas/kinds/flow/RunOptionsSection.tsx +++ b/apps/agent-forge/src/canvas/kinds/flow/RunOptionsSection.tsx @@ -6,7 +6,7 @@ * object and deleted when Attempts is cleared; `timeoutMs` deletes on * empty. */ -import type { NodeRetry } from 'lousho/flows'; +import type { NodeRetry } from '@linuxdevil/lousho/flows'; import { InspectorSection } from '../../../components/InspectorSection'; import { OptionalNumberField } from '../../fields/OptionalNumberField'; import type { KindFieldsProps } from '../index'; diff --git a/apps/agent-forge/src/canvas/templates.ts b/apps/agent-forge/src/canvas/templates.ts index f1a81afa9..c98544768 100644 --- a/apps/agent-forge/src/canvas/templates.ts +++ b/apps/agent-forge/src/canvas/templates.ts @@ -1,4 +1,4 @@ -import type { AgentSpecV2 } from 'lousho'; +import type { AgentSpecV2 } from '@linuxdevil/lousho'; import { specToGraph } from '../graph/v2/specToGraph'; import type { Graph } from '../graph/v2/types'; diff --git a/apps/agent-forge/src/components/debug/forkReplay.tsx b/apps/agent-forge/src/components/debug/forkReplay.tsx index 2f2095807..6ddccb210 100644 --- a/apps/agent-forge/src/components/debug/forkReplay.tsx +++ b/apps/agent-forge/src/components/debug/forkReplay.tsx @@ -1,5 +1,5 @@ import { useEffect, useState } from 'react'; -import type { TrajectoryStep } from 'lousho'; +import type { TrajectoryStep } from '@linuxdevil/lousho'; import { runtimeClient } from '../../runtime/runtimeClient'; import { errorMessage } from '../errorMessage'; import type { ForkRunRequest, RunComparisonPayload, RunHistoryStep } from '../../../shared/wireTypes'; diff --git a/apps/agent-forge/src/graph/v2/schemaIssues.ts b/apps/agent-forge/src/graph/v2/schemaIssues.ts index 2c37ad76d..c3daee56d 100644 --- a/apps/agent-forge/src/graph/v2/schemaIssues.ts +++ b/apps/agent-forge/src/graph/v2/schemaIssues.ts @@ -6,7 +6,7 @@ * schema's own complaints (bad enums, patterns, missing required fields, * and the "not supported yet" placeholders, which become warnings). */ -import { agentSpecV2Schema, UNSUPPORTED_PREFIX } from 'lousho/spec'; +import { agentSpecV2Schema, UNSUPPORTED_PREFIX } from '@linuxdevil/lousho/spec'; import type { AgentSpecV2 } from './specTypes'; import type { Problem } from './types'; diff --git a/apps/agent-forge/src/graph/v2/specTypes.ts b/apps/agent-forge/src/graph/v2/specTypes.ts index f398a22fc..17cd84131 100644 --- a/apps/agent-forge/src/graph/v2/specTypes.ts +++ b/apps/agent-forge/src/graph/v2/specTypes.ts @@ -9,10 +9,10 @@ */ /** The flow the run follows (from the published `lousho/flows` subpath). */ -export type { AgentFlow } from 'lousho/flows'; +export type { AgentFlow } from '@linuxdevil/lousho/flows'; /** The agent spec the canvas edits, plus its subagent union. */ -export type { AgentSpecV2, SubagentSpec } from 'lousho/spec'; +export type { AgentSpecV2, SubagentSpec } from '@linuxdevil/lousho/spec'; /** * One `spec.tools` entry, plus the SDK's own lists for the tool Inspector: * the credential keys each integration takes (`INTEGRATION_CREDENTIALS`), @@ -20,48 +20,48 @@ export type { AgentSpecV2, SubagentSpec } from 'lousho/spec'; * pattern a `ToolRef`'s `id` follows (`SPEC_ID_PATTERN`). Value exports * alongside the type exports — the seam is the same. */ -export type { ToolRef, IntegrationName, OpenApiToolRefOptions } from 'lousho/spec'; -export { INTEGRATION_CREDENTIALS, SECRET_CREDENTIALS } from 'lousho/spec'; -export { isSafeRelativePath, SPEC_ID_PATTERN } from 'lousho/spec'; +export type { ToolRef, IntegrationName, OpenApiToolRefOptions } from '@linuxdevil/lousho/spec'; +export { INTEGRATION_CREDENTIALS, SECRET_CREDENTIALS } from '@linuxdevil/lousho/spec'; +export { isSafeRelativePath, SPEC_ID_PATTERN } from '@linuxdevil/lousho/spec'; /** A reference to an env var or a named Studio secret. */ -export type { SecretRef } from 'lousho/spec'; +export type { SecretRef } from '@linuxdevil/lousho/spec'; /** One `spec.skills` entry. */ -export type { SkillRef } from 'lousho/spec'; +export type { SkillRef } from '@linuxdevil/lousho/spec'; /** One `spec.memory` entry, plus its `scope`/`provider` union members for the Inspector's sub-forms. */ -export type { MemorySpec, MemoryScopeSpec, MemoryProviderSpec } from 'lousho/spec'; +export type { MemorySpec, MemoryScopeSpec, MemoryProviderSpec } from '@linuxdevil/lousho/spec'; /** * One `spec.mcpServers` value. The SDK names it `McpServerSpecV2`; the canvas * uses the shorter `McpServerSpec` everywhere, aliased once here. */ -export type { McpServerSpecV2 as McpServerSpec } from 'lousho/spec'; +export type { McpServerSpecV2 as McpServerSpec } from '@linuxdevil/lousho/spec'; /** The `mcpServers` record-key pattern the SDK enforces (`^[A-Za-z0-9_-]{1,64}$`). */ -export { MCP_SERVER_NAME } from 'lousho/spec'; +export { MCP_SERVER_NAME } from '@linuxdevil/lousho/spec'; /** The longest approval deadline the spec schema accepts (`approvalTtlMs`/`ttlMs`), in ms. */ -export { MAX_APPROVAL_TTL_MS } from 'lousho/spec'; +export { MAX_APPROVAL_TTL_MS } from '@linuxdevil/lousho/spec'; /** One `spec.handoffs` entry. */ -export type { HandoffSpec } from 'lousho/spec'; +export type { HandoffSpec } from '@linuxdevil/lousho/spec'; /** * One `spec.guardrails` entry, plus the `guardrailSidesOf` helper the * Inspector and the kind validator share to read `on` (a side or a side * list; absent means input and output). */ -export type { GuardrailSpec } from 'lousho/spec'; -export { guardrailSidesOf, REWRITABLE_GUARDRAILS } from 'lousho/spec'; +export type { GuardrailSpec } from '@linuxdevil/lousho/spec'; +export { guardrailSidesOf, REWRITABLE_GUARDRAILS } from '@linuxdevil/lousho/spec'; /** One `spec.permissions` entry. */ -export type { PermissionRuleSpec } from 'lousho/spec'; +export type { PermissionRuleSpec } from '@linuxdevil/lousho/spec'; /** One `spec.hooks` entry, plus the `inline` member's shape for the hook Inspector. */ -export type { HookSpec, InlineHookSpec } from 'lousho/spec'; +export type { HookSpec, InlineHookSpec } from '@linuxdevil/lousho/spec'; /** The agent's `spec.output` section (`jsonSchema` plus `promptSchema`/`responseFormat`). */ -export type { OutputSpecV2 } from 'lousho/spec'; +export type { OutputSpecV2 } from '@linuxdevil/lousho/spec'; /** The executable `AgentFlow` step types (excludes editor-only shapes) — the flow kind's validation list. */ -export { EXECUTABLE_TYPES } from 'lousho/spec'; +export { EXECUTABLE_TYPES } from '@linuxdevil/lousho/spec'; /** * One `spec.triggers` entry, plus the channel union and the required * `secrets` keys per channel kind (`CHANNEL_SECRET_KEYS`) — the SDK's own * list, shared by the trigger Inspector's prefill and `triggerRules`. */ -export type { TriggerSpec, ChannelKind } from 'lousho/spec'; -export { CHANNEL_SECRET_KEYS } from 'lousho/spec'; +export type { TriggerSpec, ChannelKind } from '@linuxdevil/lousho/spec'; +export { CHANNEL_SECRET_KEYS } from '@linuxdevil/lousho/spec'; /** * One `spec.abortListeners` entry — a trigger-correlated abort rule. The SDK diff --git a/apps/agent-forge/src/persistence/AgentStore.ts b/apps/agent-forge/src/persistence/AgentStore.ts index 28b050011..13a813b17 100644 --- a/apps/agent-forge/src/persistence/AgentStore.ts +++ b/apps/agent-forge/src/persistence/AgentStore.ts @@ -1,4 +1,4 @@ -import type { AgentSpec, AgentSpecV2 } from 'lousho'; +import type { AgentSpec, AgentSpecV2 } from '@linuxdevil/lousho'; import type { AgentListItem, AgentRecord, CanvasLayout } from '../../shared/wireTypes'; /** diff --git a/apps/agent-forge/src/persistence/HttpAgentStore.ts b/apps/agent-forge/src/persistence/HttpAgentStore.ts index 7fcd1a9d2..98989561f 100644 --- a/apps/agent-forge/src/persistence/HttpAgentStore.ts +++ b/apps/agent-forge/src/persistence/HttpAgentStore.ts @@ -1,4 +1,4 @@ -import type { AgentSpecV2 } from 'lousho'; +import type { AgentSpecV2 } from '@linuxdevil/lousho'; import type { AgentListItem, AgentRecord, CanvasLayout } from '../../shared/wireTypes'; import type { AgentStore } from './AgentStore'; diff --git a/apps/agent-forge/src/persistence/__tests__/HttpAgentStore.test.ts b/apps/agent-forge/src/persistence/__tests__/HttpAgentStore.test.ts index c5963e7c7..230ee8e11 100644 --- a/apps/agent-forge/src/persistence/__tests__/HttpAgentStore.test.ts +++ b/apps/agent-forge/src/persistence/__tests__/HttpAgentStore.test.ts @@ -1,5 +1,5 @@ import { describe, it, expect, beforeEach } from 'vitest'; -import type { AgentSpecV2 } from 'lousho'; +import type { AgentSpecV2 } from '@linuxdevil/lousho'; import { HttpAgentStore, type AgentApi } from '../HttpAgentStore'; import type { AgentListItem, AgentMeta, AgentRecord, CanvasLayout } from '../../../shared/wireTypes'; diff --git a/apps/agent-forge/src/persistence/__tests__/fsAgentStore.test.ts b/apps/agent-forge/src/persistence/__tests__/fsAgentStore.test.ts index f3efdf9f5..2424f2220 100644 --- a/apps/agent-forge/src/persistence/__tests__/fsAgentStore.test.ts +++ b/apps/agent-forge/src/persistence/__tests__/fsAgentStore.test.ts @@ -3,7 +3,7 @@ import * as os from 'node:os'; import * as path from 'node:path'; import { parse as parseYaml, stringify as stringifyYaml } from 'yaml'; import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; -import type { AgentSpec, AgentSpecV2 } from 'lousho'; +import type { AgentSpec, AgentSpecV2 } from '@linuxdevil/lousho'; import { AgentRepo } from '../../../server/storage/agentRepo'; import { createFsAgentStore } from '../fsAgentStore'; diff --git a/apps/agent-forge/src/persistence/__tests__/importExport.test.ts b/apps/agent-forge/src/persistence/__tests__/importExport.test.ts index a84680837..e0d348850 100644 --- a/apps/agent-forge/src/persistence/__tests__/importExport.test.ts +++ b/apps/agent-forge/src/persistence/__tests__/importExport.test.ts @@ -1,5 +1,5 @@ import { describe, expect, it } from 'vitest'; -import type { AgentSpecV2 } from 'lousho'; +import type { AgentSpecV2 } from '@linuxdevil/lousho'; import { downloadSpec, importSpecFile, parseSpecText, serializeSpec } from '../importExport'; const spec: AgentSpecV2 = { diff --git a/apps/agent-forge/src/persistence/fsAgentStore.ts b/apps/agent-forge/src/persistence/fsAgentStore.ts index 644266cd5..093013a23 100644 --- a/apps/agent-forge/src/persistence/fsAgentStore.ts +++ b/apps/agent-forge/src/persistence/fsAgentStore.ts @@ -1,4 +1,4 @@ -import type { AgentSpec } from 'lousho'; +import type { AgentSpec } from '@linuxdevil/lousho'; import type { LegacyAgentStore, LegacyAgentStoreEntry } from './AgentStore'; import { AgentRepo } from '../../server/storage/agentRepo'; import { mergeLegacySave, toLegacySpec } from '../../server/storage/legacySpec'; diff --git a/apps/agent-forge/src/persistence/importExport.ts b/apps/agent-forge/src/persistence/importExport.ts index 6bc0ad895..640d72290 100644 --- a/apps/agent-forge/src/persistence/importExport.ts +++ b/apps/agent-forge/src/persistence/importExport.ts @@ -1,9 +1,9 @@ import { parse as parseYaml, stringify as stringifyYaml } from 'yaml'; -import type { AgentSpecV2 } from 'lousho/spec'; +import type { AgentSpecV2 } from '@linuxdevil/lousho/spec'; // `lousho/spec` is the SDK's browser-safe spec entry (schema, types and // pure helpers only, no Node-only deps), so this value import is safe in // the Vite/browser bundle where the package root is not. -import { agentSpecV2Schema } from 'lousho/spec'; +import { agentSpecV2Schema } from '@linuxdevil/lousho/spec'; export type ExportFormat = 'yaml' | 'json'; diff --git a/apps/agent-forge/src/runtime/runtimeClient.ts b/apps/agent-forge/src/runtime/runtimeClient.ts index 3a39f5980..578699811 100644 --- a/apps/agent-forge/src/runtime/runtimeClient.ts +++ b/apps/agent-forge/src/runtime/runtimeClient.ts @@ -6,7 +6,7 @@ * so the two can evolve together without every component knowing the HTTP * details. */ -import type { AgentSpecV2 } from 'lousho'; +import type { AgentSpecV2 } from '@linuxdevil/lousho'; import type { AddWorkspaceRequest, AgentListItem, diff --git a/apps/agent-forge/src/screens/agentDetail/AgentOverviewTab.tsx b/apps/agent-forge/src/screens/agentDetail/AgentOverviewTab.tsx index b44038fc6..8e385cd8e 100644 --- a/apps/agent-forge/src/screens/agentDetail/AgentOverviewTab.tsx +++ b/apps/agent-forge/src/screens/agentDetail/AgentOverviewTab.tsx @@ -1,6 +1,6 @@ import { ChevronRight, TriangleAlert } from 'lucide-react'; import { useEffect, useState, type ReactNode } from 'react'; -import type { AgentSpecV2 } from 'lousho/spec'; +import type { AgentSpecV2 } from '@linuxdevil/lousho/spec'; import type { ChannelTriggerStatus, TraceSummaryPayload } from '../../../shared/wireTypes'; import { agentTabPath, BUILT_AGENT_TABS } from '../agents/agentTabs'; diff --git a/apps/agent-forge/src/screens/agentDetail/__tests__/AgentOverviewTab.test.tsx b/apps/agent-forge/src/screens/agentDetail/__tests__/AgentOverviewTab.test.tsx index e144fb097..9deb36fb7 100644 --- a/apps/agent-forge/src/screens/agentDetail/__tests__/AgentOverviewTab.test.tsx +++ b/apps/agent-forge/src/screens/agentDetail/__tests__/AgentOverviewTab.test.tsx @@ -3,7 +3,7 @@ import { beforeEach, describe, expect, it, vi } from 'vitest'; import type { AgentRecord, Requirement, TraceSummaryPayload } from '../../../../shared/wireTypes'; import { render } from '../../../ui/__tests__/render'; -import type { AgentSpecV2 } from 'lousho/spec'; +import type { AgentSpecV2 } from '@linuxdevil/lousho/spec'; const mocks = vi.hoisted(() => ({ listTraces: vi.fn(), channelTriggerStatus: vi.fn() })); diff --git a/apps/agent-forge/src/screens/agentDetail/__tests__/DraftCard.test.tsx b/apps/agent-forge/src/screens/agentDetail/__tests__/DraftCard.test.tsx index f6562cdbd..96cdb5510 100644 --- a/apps/agent-forge/src/screens/agentDetail/__tests__/DraftCard.test.tsx +++ b/apps/agent-forge/src/screens/agentDetail/__tests__/DraftCard.test.tsx @@ -2,7 +2,7 @@ import { act } from 'react'; import { beforeEach, describe, expect, it, vi } from 'vitest'; import type { AgentRecord, DraftAutoReview, DraftView, SpecChange } from '../../../../shared/wireTypes'; -import type { AgentSpecV2 } from 'lousho/spec'; +import type { AgentSpecV2 } from '@linuxdevil/lousho/spec'; import { render } from '../../../ui/__tests__/render'; import { ToastProvider } from '../../../ui/Toast'; diff --git a/apps/agent-forge/src/screens/agentDetail/__tests__/SuggestionsTab.test.tsx b/apps/agent-forge/src/screens/agentDetail/__tests__/SuggestionsTab.test.tsx index 835b78632..7af7b970c 100644 --- a/apps/agent-forge/src/screens/agentDetail/__tests__/SuggestionsTab.test.tsx +++ b/apps/agent-forge/src/screens/agentDetail/__tests__/SuggestionsTab.test.tsx @@ -2,7 +2,7 @@ import { act } from 'react'; import { beforeEach, describe, expect, it, vi } from 'vitest'; import type { AgentRecord, DraftView, PromptSuggestion, StudioEvent } from '../../../../shared/wireTypes'; -import type { AgentSpecV2 } from 'lousho/spec'; +import type { AgentSpecV2 } from '@linuxdevil/lousho/spec'; import { render } from '../../../ui/__tests__/render'; import { ToastProvider } from '../../../ui/Toast'; diff --git a/apps/agent-forge/src/screens/agentDetail/__tests__/VersionsTab.test.tsx b/apps/agent-forge/src/screens/agentDetail/__tests__/VersionsTab.test.tsx index 1ae989ad5..b37e525a3 100644 --- a/apps/agent-forge/src/screens/agentDetail/__tests__/VersionsTab.test.tsx +++ b/apps/agent-forge/src/screens/agentDetail/__tests__/VersionsTab.test.tsx @@ -2,7 +2,7 @@ import { act } from 'react'; import { beforeEach, describe, expect, it, vi } from 'vitest'; import type { AgentRecord, StudioEvent, VersionListItem } from '../../../../shared/wireTypes'; -import type { AgentSpecV2 } from 'lousho/spec'; +import type { AgentSpecV2 } from '@linuxdevil/lousho/spec'; import { render } from '../../../ui/__tests__/render'; import { ToastProvider } from '../../../ui/Toast'; diff --git a/apps/agent-forge/src/screens/agentDetail/__tests__/overviewModel.test.ts b/apps/agent-forge/src/screens/agentDetail/__tests__/overviewModel.test.ts index 39b9de574..a75d77a0b 100644 --- a/apps/agent-forge/src/screens/agentDetail/__tests__/overviewModel.test.ts +++ b/apps/agent-forge/src/screens/agentDetail/__tests__/overviewModel.test.ts @@ -1,5 +1,5 @@ import { describe, expect, it } from 'vitest'; -import type { AgentSpecV2 } from 'lousho/spec'; +import type { AgentSpecV2 } from '@linuxdevil/lousho/spec'; import type { Requirement } from '../../../../shared/wireTypes'; import { capabilityRows, missingRequirements, permissionRuleText, safetySummary, triggerLine } from '../overviewModel'; diff --git a/apps/agent-forge/src/screens/agentDetail/code/CodeTab.tsx b/apps/agent-forge/src/screens/agentDetail/code/CodeTab.tsx index b6c341167..718e9080e 100644 --- a/apps/agent-forge/src/screens/agentDetail/code/CodeTab.tsx +++ b/apps/agent-forge/src/screens/agentDetail/code/CodeTab.tsx @@ -4,7 +4,7 @@ import { stringify as stringifyYaml } from 'yaml'; // `lousho/spec` is the SDK's browser-safe spec entry (S23): `specToCode` // (S18) and `specToAgentDir` (S19) are both pure renderers, so the Code tab // generates every view in the browser with no server round-trip. -import { specToAgentDir, specToCode, type AgentSpecV2 } from 'lousho/spec'; +import { specToAgentDir, specToCode, type AgentSpecV2 } from '@linuxdevil/lousho/spec'; import { errorMessage } from '../../../components/errorMessage'; import { downloadText } from '../../../persistence/importExport'; diff --git a/apps/agent-forge/src/screens/agentDetail/code/__tests__/CodeTab.test.tsx b/apps/agent-forge/src/screens/agentDetail/code/__tests__/CodeTab.test.tsx index 2cf82fe9d..b01f27c2e 100644 --- a/apps/agent-forge/src/screens/agentDetail/code/__tests__/CodeTab.test.tsx +++ b/apps/agent-forge/src/screens/agentDetail/code/__tests__/CodeTab.test.tsx @@ -2,7 +2,7 @@ import { act } from 'react'; import { beforeEach, describe, expect, it, vi } from 'vitest'; import type { AgentRecord } from '../../../../../shared/wireTypes'; -import type { AgentSpecV2 } from 'lousho/spec'; +import type { AgentSpecV2 } from '@linuxdevil/lousho/spec'; import { parseHash } from '../../../../router/router'; import { render } from '../../../../ui/__tests__/render'; import { ToastProvider } from '../../../../ui/Toast'; @@ -20,7 +20,7 @@ const mocks = vi.hoisted(() => ({ // The Code tab calls the real SDK generators in the browser; here they are // stubbed so the assertions are about the tab, not the generators. -vi.mock('lousho/spec', () => ({ +vi.mock('@linuxdevil/lousho/spec', () => ({ specToCode: () => mocks.specToCode(), specToAgentDir: () => mocks.specToAgentDir(), })); diff --git a/apps/agent-forge/src/screens/agentDetail/configure/ConfigureTab.tsx b/apps/agent-forge/src/screens/agentDetail/configure/ConfigureTab.tsx index 03aaa830f..172946e5f 100644 --- a/apps/agent-forge/src/screens/agentDetail/configure/ConfigureTab.tsx +++ b/apps/agent-forge/src/screens/agentDetail/configure/ConfigureTab.tsx @@ -1,6 +1,6 @@ import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; -import { agentSpecV2JsonSchema } from 'lousho/spec'; +import { agentSpecV2JsonSchema } from '@linuxdevil/lousho/spec'; import { useQueryState } from '../../../router/useRoute'; import { useAppState } from '../../../state/AppState'; diff --git a/apps/agent-forge/src/screens/agentDetail/configure/__tests__/ConfigureTab.test.tsx b/apps/agent-forge/src/screens/agentDetail/configure/__tests__/ConfigureTab.test.tsx index 6fa7094c2..47344bbf5 100644 --- a/apps/agent-forge/src/screens/agentDetail/configure/__tests__/ConfigureTab.test.tsx +++ b/apps/agent-forge/src/screens/agentDetail/configure/__tests__/ConfigureTab.test.tsx @@ -2,7 +2,7 @@ import { act, useCallback, useState } from 'react'; import { beforeEach, describe, expect, it, vi } from 'vitest'; import type { AgentRecord } from '../../../../../shared/wireTypes'; -import type { AgentSpecV2 } from 'lousho/spec'; +import type { AgentSpecV2 } from '@linuxdevil/lousho/spec'; import { render } from '../../../../ui/__tests__/render'; import { ToastProvider } from '../../../../ui/Toast'; diff --git a/apps/agent-forge/src/screens/agentDetail/configure/__tests__/sections.test.ts b/apps/agent-forge/src/screens/agentDetail/configure/__tests__/sections.test.ts index 8882c9bca..8d7e6675b 100644 --- a/apps/agent-forge/src/screens/agentDetail/configure/__tests__/sections.test.ts +++ b/apps/agent-forge/src/screens/agentDetail/configure/__tests__/sections.test.ts @@ -1,6 +1,6 @@ import { describe, expect, it } from 'vitest'; -import { agentSpecV2JsonSchema, type AgentSpecV2 } from 'lousho/spec'; +import { agentSpecV2JsonSchema, type AgentSpecV2 } from '@linuxdevil/lousho/spec'; import type { JsonSchema } from '../../../../ui/schemaForm/types'; import { diff --git a/apps/agent-forge/src/screens/agentDetail/configure/sections.ts b/apps/agent-forge/src/screens/agentDetail/configure/sections.ts index e3b890c1e..ee6814097 100644 --- a/apps/agent-forge/src/screens/agentDetail/configure/sections.ts +++ b/apps/agent-forge/src/screens/agentDetail/configure/sections.ts @@ -4,7 +4,7 @@ * SchemaForm draws, and the dirty bookkeeping that compares the draft * against the saved spec section by section. */ -import type { AgentSpecV2 } from 'lousho/spec'; +import type { AgentSpecV2 } from '@linuxdevil/lousho/spec'; import { stableStringify } from '../../../../shared/stableJson'; import type { JsonSchema } from '../../../ui/schemaForm/types'; diff --git a/apps/agent-forge/src/screens/agentDetail/configure/useConfigureDraft.ts b/apps/agent-forge/src/screens/agentDetail/configure/useConfigureDraft.ts index 18a251ffb..c4f2f3d09 100644 --- a/apps/agent-forge/src/screens/agentDetail/configure/useConfigureDraft.ts +++ b/apps/agent-forge/src/screens/agentDetail/configure/useConfigureDraft.ts @@ -1,6 +1,6 @@ import { useCallback, useEffect, useMemo, useState } from 'react'; -import { agentSpecV2JsonSchema, type AgentSpecV2 } from 'lousho/spec'; +import { agentSpecV2JsonSchema, type AgentSpecV2 } from '@linuxdevil/lousho/spec'; import type { AgentRecord } from '../../../../shared/wireTypes'; import { RuntimeApiError, runtimeClient } from '../../../runtime/runtimeClient'; diff --git a/apps/agent-forge/src/screens/agentDetail/overviewModel.ts b/apps/agent-forge/src/screens/agentDetail/overviewModel.ts index dca27b435..508bd9330 100644 --- a/apps/agent-forge/src/screens/agentDetail/overviewModel.ts +++ b/apps/agent-forge/src/screens/agentDetail/overviewModel.ts @@ -5,7 +5,7 @@ * capability rows and the safety rows. */ import { Clock, MessageSquare, Radio, Webhook, Workflow, type LucideIcon } from 'lucide-react'; -import type { AgentSpecV2 } from 'lousho/spec'; +import type { AgentSpecV2 } from '@linuxdevil/lousho/spec'; import type { Requirement } from '../../../shared/wireTypes'; diff --git a/apps/agent-forge/src/screens/agentDetail/runs/__tests__/RunsTab.test.tsx b/apps/agent-forge/src/screens/agentDetail/runs/__tests__/RunsTab.test.tsx index 6233bb90a..6dd1722ed 100644 --- a/apps/agent-forge/src/screens/agentDetail/runs/__tests__/RunsTab.test.tsx +++ b/apps/agent-forge/src/screens/agentDetail/runs/__tests__/RunsTab.test.tsx @@ -2,7 +2,7 @@ import { act } from 'react'; import { beforeEach, describe, expect, it, vi } from 'vitest'; import type { AgentRecord, TraceSummaryPayload } from '../../../../../shared/wireTypes'; -import type { AgentSpecV2 } from 'lousho/spec'; +import type { AgentSpecV2 } from '@linuxdevil/lousho/spec'; import type { ObserveScoreRow } from '../../../../observe/types'; import { render } from '../../../../ui/__tests__/render'; diff --git a/apps/agent-forge/src/screens/codeAgent/CodeAgentScreen.tsx b/apps/agent-forge/src/screens/codeAgent/CodeAgentScreen.tsx index 5f2fd4ebd..2e10ade7b 100644 --- a/apps/agent-forge/src/screens/codeAgent/CodeAgentScreen.tsx +++ b/apps/agent-forge/src/screens/codeAgent/CodeAgentScreen.tsx @@ -9,7 +9,7 @@ */ import { useEffect, useMemo, useState } from 'react'; import { Folder, Info } from 'lucide-react'; -import type { AgentSpecV2 } from 'lousho'; +import type { AgentSpecV2 } from '@linuxdevil/lousho'; import { stringify } from 'yaml'; import type { Graph } from '../../graph/v2/types'; import { specToGraph } from '../../graph/v2/specToGraph'; diff --git a/apps/agent-forge/src/screens/codeAgent/__tests__/CodeAgentCanvas.test.tsx b/apps/agent-forge/src/screens/codeAgent/__tests__/CodeAgentCanvas.test.tsx index f805d1653..9d45751e7 100644 --- a/apps/agent-forge/src/screens/codeAgent/__tests__/CodeAgentCanvas.test.tsx +++ b/apps/agent-forge/src/screens/codeAgent/__tests__/CodeAgentCanvas.test.tsx @@ -35,7 +35,7 @@ vi.mock('../../../state/AppState', () => ({ }), })); -import type { AgentSpecV2 } from 'lousho'; +import type { AgentSpecV2 } from '@linuxdevil/lousho'; import { specToGraph } from '../../../graph/v2/specToGraph'; import type { Graph } from '../../../graph/v2/types'; import { CodeAgentCanvas, CodeAgentInspector } from '../CodeAgentCanvas'; diff --git a/apps/agent-forge/src/screens/codeAgent/__tests__/CodeAgentScreen.test.tsx b/apps/agent-forge/src/screens/codeAgent/__tests__/CodeAgentScreen.test.tsx index d31cddabe..84db34d48 100644 --- a/apps/agent-forge/src/screens/codeAgent/__tests__/CodeAgentScreen.test.tsx +++ b/apps/agent-forge/src/screens/codeAgent/__tests__/CodeAgentScreen.test.tsx @@ -48,7 +48,7 @@ vi.mock('../../../canvas/layout', async (importActual) => { return { ...actual, autoLayout: vi.fn(async (graph: unknown) => graph) }; }); -import type { AgentSpecV2 } from 'lousho'; +import type { AgentSpecV2 } from '@linuxdevil/lousho'; import type { AgentRecord } from '../../../../shared/wireTypes'; import { RuntimeApiError } from '../../../runtime/runtimeClient'; import { CodeAgentScreen } from '../CodeAgentScreen'; diff --git a/apps/agent-forge/src/screens/workspaces/OverviewSection.tsx b/apps/agent-forge/src/screens/workspaces/OverviewSection.tsx index 5fccaf920..ffb9ebb2d 100644 --- a/apps/agent-forge/src/screens/workspaces/OverviewSection.tsx +++ b/apps/agent-forge/src/screens/workspaces/OverviewSection.tsx @@ -1,6 +1,6 @@ import { useState } from 'react'; -import workspaceJsonSchema from 'lousho/schemas/lousho.workspace.v1.json'; -import type { ProblemView } from 'lousho/workspaces/client'; +import workspaceJsonSchema from '@linuxdevil/lousho/schemas/lousho.workspace.v1.json'; +import type { ProblemView } from '@linuxdevil/lousho/workspaces/client'; import { SchemaForm } from '../../ui/schemaForm/SchemaForm'; import type { JsonSchema } from '../../ui/schemaForm/types'; import { useWorkspace } from './WorkspaceContext'; diff --git a/apps/agent-forge/src/screens/workspaces/PagesSection.tsx b/apps/agent-forge/src/screens/workspaces/PagesSection.tsx index 1913e1597..7bf2ded30 100644 --- a/apps/agent-forge/src/screens/workspaces/PagesSection.tsx +++ b/apps/agent-forge/src/screens/workspaces/PagesSection.tsx @@ -1,6 +1,6 @@ import { useEffect, useState } from 'react'; import { ArrowDown, ArrowUp, Trash2 } from 'lucide-react'; -import type { PageDetail } from 'lousho/workspaces/client'; +import type { PageDetail } from '@linuxdevil/lousho/workspaces/client'; import { ConfirmDialog, Dialog } from '../../ui/Dialog'; import { EmptyState } from '../../ui/EmptyState'; import { Field } from '../../ui/Field'; diff --git a/apps/agent-forge/src/screens/workspaces/WorkspaceContext.tsx b/apps/agent-forge/src/screens/workspaces/WorkspaceContext.tsx index 6cc40f9fa..ec3cbccda 100644 --- a/apps/agent-forge/src/screens/workspaces/WorkspaceContext.tsx +++ b/apps/agent-forge/src/screens/workspaces/WorkspaceContext.tsx @@ -1,5 +1,5 @@ import { createContext, useContext } from 'react'; -import type { DraftView, ProblemView, WorkspaceDetail } from 'lousho/workspaces/client'; +import type { DraftView, ProblemView, WorkspaceDetail } from '@linuxdevil/lousho/workspaces/client'; import type { OpsSender } from './useWorkspaceOps'; export interface WorkspaceContextValue { diff --git a/apps/agent-forge/src/screens/workspaces/WorkspaceRoute.tsx b/apps/agent-forge/src/screens/workspaces/WorkspaceRoute.tsx index 4b5e4a7ce..4e1f8733d 100644 --- a/apps/agent-forge/src/screens/workspaces/WorkspaceRoute.tsx +++ b/apps/agent-forge/src/screens/workspaces/WorkspaceRoute.tsx @@ -1,7 +1,7 @@ import './workspaces.css'; import { useState } from 'react'; import { CircleAlert, SearchX } from 'lucide-react'; -import type { ApiClient } from 'lousho/workspaces/client'; +import type { ApiClient } from '@linuxdevil/lousho/workspaces/client'; import { matchRoute } from '../../router/router'; import { useRoute } from '../../router/useRoute'; import { EmptyState } from '../../ui/EmptyState'; diff --git a/apps/agent-forge/src/screens/workspaces/WorkspacesScreen.tsx b/apps/agent-forge/src/screens/workspaces/WorkspacesScreen.tsx index d25ae0bf6..821a57ebf 100644 --- a/apps/agent-forge/src/screens/workspaces/WorkspacesScreen.tsx +++ b/apps/agent-forge/src/screens/workspaces/WorkspacesScreen.tsx @@ -1,6 +1,6 @@ import { useMemo } from 'react'; import { CircleAlert } from 'lucide-react'; -import type { WorkspaceSummary } from 'lousho/workspaces/client'; +import type { WorkspaceSummary } from '@linuxdevil/lousho/workspaces/client'; import { navigate } from '../../router/router'; import { useRoute } from '../../router/useRoute'; import { ScreenHeader } from '../../shell/ScreenHeader'; diff --git a/apps/agent-forge/src/screens/workspaces/__tests__/WorkspaceRoute.test.tsx b/apps/agent-forge/src/screens/workspaces/__tests__/WorkspaceRoute.test.tsx index 95cf9837f..ba78840f2 100644 --- a/apps/agent-forge/src/screens/workspaces/__tests__/WorkspaceRoute.test.tsx +++ b/apps/agent-forge/src/screens/workspaces/__tests__/WorkspaceRoute.test.tsx @@ -1,5 +1,5 @@ import { describe, expect, it } from 'vitest'; -import { ROUTE_NAMES, WorkspaceApiError } from 'lousho/workspaces/client'; +import { ROUTE_NAMES, WorkspaceApiError } from '@linuxdevil/lousho/workspaces/client'; import { WorkspaceRoute } from '../WorkspaceRoute'; import { buttonByName, click, conflict, flush, renderWithClient, spyClient, typeInto } from './workspaceTestKit'; diff --git a/apps/agent-forge/src/screens/workspaces/__tests__/WorkspacesScreen.test.tsx b/apps/agent-forge/src/screens/workspaces/__tests__/WorkspacesScreen.test.tsx index 56c0a879a..a5f5d34d2 100644 --- a/apps/agent-forge/src/screens/workspaces/__tests__/WorkspacesScreen.test.tsx +++ b/apps/agent-forge/src/screens/workspaces/__tests__/WorkspacesScreen.test.tsx @@ -1,10 +1,10 @@ import { act } from 'react'; import { beforeEach, describe, expect, it, vi } from 'vitest'; -import type { WorkspaceSummary } from 'lousho/workspaces/client'; +import type { WorkspaceSummary } from '@linuxdevil/lousho/workspaces/client'; import { WorkspacesScreen } from '../WorkspacesScreen'; import type { ScreenHeaderProps } from '../../../shell/ScreenHeader'; -import { WorkspaceApiError } from 'lousho/workspaces/client'; +import { WorkspaceApiError } from '@linuxdevil/lousho/workspaces/client'; import { buttonByName, click, flush, renderWithClient, spyClient } from './workspaceTestKit'; vi.mock('../../../shell/ScreenHeader', () => ({ diff --git a/apps/agent-forge/src/screens/workspaces/__tests__/workspaceTestKit.tsx b/apps/agent-forge/src/screens/workspaces/__tests__/workspaceTestKit.tsx index a9e64912c..ec6651a86 100644 --- a/apps/agent-forge/src/screens/workspaces/__tests__/workspaceTestKit.tsx +++ b/apps/agent-forge/src/screens/workspaces/__tests__/workspaceTestKit.tsx @@ -1,5 +1,5 @@ import { act, type ReactNode } from 'react'; -import { fixtureClient, WorkspaceApiError, type ApiClient, type JsonRouteName } from 'lousho/workspaces/client'; +import { fixtureClient, WorkspaceApiError, type ApiClient, type JsonRouteName } from '@linuxdevil/lousho/workspaces/client'; import { render } from '../../../ui/__tests__/render'; import { ToastProvider } from '../../../ui/Toast'; import { WorkspacesClientProvider } from '../workspacesClient'; diff --git a/apps/agent-forge/src/screens/workspaces/useClientQuery.ts b/apps/agent-forge/src/screens/workspaces/useClientQuery.ts index cec164e8b..4c36b4e09 100644 --- a/apps/agent-forge/src/screens/workspaces/useClientQuery.ts +++ b/apps/agent-forge/src/screens/workspaces/useClientQuery.ts @@ -1,6 +1,6 @@ // fallow-ignore-file code-duplication -- intentionally the useAgentRecord load shape (ticket P2.5 step 3). import { useCallback, useEffect, useRef, useState } from 'react'; -import { WorkspaceApiError, type ApiClient } from 'lousho/workspaces/client'; +import { WorkspaceApiError, type ApiClient } from '@linuxdevil/lousho/workspaces/client'; import { useWorkspacesClient } from './workspacesClient'; export interface QueryState { diff --git a/apps/agent-forge/src/screens/workspaces/useWorkspaceOps.ts b/apps/agent-forge/src/screens/workspaces/useWorkspaceOps.ts index 6a110bb6f..66aa9c344 100644 --- a/apps/agent-forge/src/screens/workspaces/useWorkspaceOps.ts +++ b/apps/agent-forge/src/screens/workspaces/useWorkspaceOps.ts @@ -1,5 +1,5 @@ import { useCallback, useState } from 'react'; -import { WorkspaceApiError, type ApplyOpsView, type ProblemView } from 'lousho/workspaces/client'; +import { WorkspaceApiError, type ApplyOpsView, type ProblemView } from '@linuxdevil/lousho/workspaces/client'; import { useToast } from '../../ui/Toast'; import { useWorkspacesClient } from './workspacesClient'; import type { AuthoringOp } from './workspaceTypes'; diff --git a/apps/agent-forge/src/screens/workspaces/workspaceTypes.ts b/apps/agent-forge/src/screens/workspaces/workspaceTypes.ts index 5cfc8c86d..07bc07651 100644 --- a/apps/agent-forge/src/screens/workspaces/workspaceTypes.ts +++ b/apps/agent-forge/src/screens/workspaces/workspaceTypes.ts @@ -1,4 +1,4 @@ -import type { ApplyOpsBody, HarnessView, MainHarnessView, PageDetail, WorkspaceDetail } from 'lousho/workspaces/client'; +import type { ApplyOpsBody, HarnessView, MainHarnessView, PageDetail, WorkspaceDetail } from '@linuxdevil/lousho/workspaces/client'; export type AuthoringOp = ApplyOpsBody['ops'][number]; export type WorkspaceManifest = WorkspaceDetail['manifest']; diff --git a/apps/agent-forge/src/screens/workspaces/workspacesClient.tsx b/apps/agent-forge/src/screens/workspaces/workspacesClient.tsx index 6d39119b2..ea3166968 100644 --- a/apps/agent-forge/src/screens/workspaces/workspacesClient.tsx +++ b/apps/agent-forge/src/screens/workspaces/workspacesClient.tsx @@ -1,5 +1,5 @@ import { createContext, useContext, useMemo, type ReactNode } from 'react'; -import { createFetchClient, type ApiClient } from 'lousho/workspaces/client'; +import { createFetchClient, type ApiClient } from '@linuxdevil/lousho/workspaces/client'; import { STUDIO_TOKEN_HEADER, loadStudioToken } from '../../runtime/studioToken'; // fallow-ignore-next-line unused-export -- contract surface: P2.21's password auth changes its headers; tests inject their own client. diff --git a/apps/agent-forge/src/state/AppState.tsx b/apps/agent-forge/src/state/AppState.tsx index 81cf388ef..0cd1c02e1 100644 --- a/apps/agent-forge/src/state/AppState.tsx +++ b/apps/agent-forge/src/state/AppState.tsx @@ -1,6 +1,6 @@ import { createContext, useCallback, useContext, useEffect, useMemo, useRef, useState } from 'react'; import type { ReactNode } from 'react'; -import type { AgentSpecV2 } from 'lousho'; +import type { AgentSpecV2 } from '@linuxdevil/lousho'; import { mergeProposal, type ProposalOverlay } from '../graph/v2/proposal'; import { HttpAgentStore } from '../persistence/HttpAgentStore'; import { runtimeClient } from '../runtime/runtimeClient'; diff --git a/apps/agent-forge/src/state/history/__tests__/history.test.ts b/apps/agent-forge/src/state/history/__tests__/history.test.ts index 21b65d8f9..b003979fc 100644 --- a/apps/agent-forge/src/state/history/__tests__/history.test.ts +++ b/apps/agent-forge/src/state/history/__tests__/history.test.ts @@ -1,5 +1,5 @@ import { describe, expect, it } from 'vitest'; -import type { AgentSpecV2 } from 'lousho'; +import type { AgentSpecV2 } from '@linuxdevil/lousho'; import type { CanvasLayout } from '../../../../shared/wireTypes'; import { COALESCE_MS, diff --git a/apps/agent-forge/src/state/useAgentControls.ts b/apps/agent-forge/src/state/useAgentControls.ts index 6e9c14ae5..fc1ca9fa0 100644 --- a/apps/agent-forge/src/state/useAgentControls.ts +++ b/apps/agent-forge/src/state/useAgentControls.ts @@ -1,6 +1,6 @@ import { useCallback, useEffect, useState } from 'react'; import type { Dispatch, SetStateAction } from 'react'; -import type { AgentSpecV2 } from 'lousho'; +import type { AgentSpecV2 } from '@linuxdevil/lousho'; import { runtimeClient, RuntimeApiError } from '../runtime/runtimeClient'; import type { AgentRunStatusPayload, diff --git a/apps/agent-forge/src/state/useAgentDocument.ts b/apps/agent-forge/src/state/useAgentDocument.ts index e8e20eea7..3a9bbd276 100644 --- a/apps/agent-forge/src/state/useAgentDocument.ts +++ b/apps/agent-forge/src/state/useAgentDocument.ts @@ -1,5 +1,5 @@ import { useCallback, useEffect, useMemo, useRef, useState, type Dispatch, type MutableRefObject, type SetStateAction } from 'react'; -import type { AgentSpecV2 } from 'lousho'; +import type { AgentSpecV2 } from '@linuxdevil/lousho'; import type { AgentStore } from '../persistence/AgentStore'; import { graphToSpec, type GraphToSpecResult } from '../graph/v2/graphToSpec'; import { specToGraph } from '../graph/v2/specToGraph'; diff --git a/apps/agent-forge/src/state/usePins.ts b/apps/agent-forge/src/state/usePins.ts index 8f7d3bb01..68d4ebe30 100644 --- a/apps/agent-forge/src/state/usePins.ts +++ b/apps/agent-forge/src/state/usePins.ts @@ -1,6 +1,6 @@ import { useCallback, useState } from 'react'; import type { Dispatch, SetStateAction } from 'react'; -import type { AgentSpecV2 } from 'lousho'; +import type { AgentSpecV2 } from '@linuxdevil/lousho'; import { runtimeClient } from '../runtime/runtimeClient'; import type { AgentRunStatusPayload, diff --git a/bin/lousho.js b/bin/lousho.js index 0980480d8..fd7654c7f 100755 --- a/bin/lousho.js +++ b/bin/lousho.js @@ -52,7 +52,7 @@ const USAGE = [ /** * Loads a command module from the ESM build (dist/cli/*.mjs). A user's tool - * file (`tools/*.ts`) is an ES module, so its `import 'lousho'` + * file (`tools/*.ts`) is an ES module, so its `import '@linuxdevil/lousho'` * resolves to the ESM build: loading the CLI from the same build means the * agent directory and the tools it loads share one copy of the SDK. */ diff --git a/docs/acp.md b/docs/acp.md index e08df220d..59ed22c99 100644 --- a/docs/acp.md +++ b/docs/acp.md @@ -10,9 +10,9 @@ it, watch its tool calls and approve them from the editor's agent panel. ## The command ```bash -npx lousho acp agent.yaml # a spec file -npx lousho acp ./my-agent --model openai/gpt-4o # an agent directory, on another model -npx lousho acp src/agent.ts # a .ts/.js module (createAgent() options or an agent) +npx @linuxdevil/lousho acp agent.yaml # a spec file +npx @linuxdevil/lousho acp ./my-agent --model openai/gpt-4o # an agent directory, on another model +npx @linuxdevil/lousho acp src/agent.ts # a .ts/.js module (createAgent() options or an agent) ``` ```text @@ -118,7 +118,7 @@ It resolves once `input` ends, after aborting any running turn. ```ts import * as readline from 'node:readline'; -import { createAgent, serveAcp } from 'lousho'; +import { createAgent, serveAcp } from '@linuxdevil/lousho'; const agent = createAgent({ model: 'openai/gpt-4o-mini', instructions: 'You are a coding assistant.' }); diff --git a/docs/agent-directories.md b/docs/agent-directories.md index d167bd470..933ee5e50 100644 --- a/docs/agent-directories.md +++ b/docs/agent-directories.md @@ -11,7 +11,7 @@ code at any time without a rewrite. > implied; the files run with your process's full permissions. ```ts -import { loadAgentDir } from 'lousho'; +import { loadAgentDir } from '@linuxdevil/lousho'; const agent = await loadAgentDir('./my-agent'); const { text } = await agent.send('Hello!'); @@ -108,7 +108,7 @@ rule decides. ```ts // hooks.ts — the default export is a hook or a list of them -import type { AgentHook } from 'lousho'; +import type { AgentHook } from '@linuxdevil/lousho'; const audit: AgentHook = { name: 'audit', postToolCall(ctx) { @@ -120,7 +120,7 @@ export default audit; ```ts // approve.ts — the default export decides every `ask`ed call -import type { ApproveToolCall } from 'lousho'; +import type { ApproveToolCall } from '@linuxdevil/lousho'; const approve: ApproveToolCall = ({ args }) => !String(args.path ?? '').endsWith('.test.js'); export default approve; ``` @@ -145,7 +145,7 @@ that exports no tool at all is an error, as are two tools with the same name ```ts no-verify // tools/send_email.ts import { z } from 'zod'; -import { defineTool } from 'lousho'; +import { defineTool } from '@linuxdevil/lousho'; export default defineTool({ name: 'send_email', @@ -236,7 +236,7 @@ internal helper of the generated bundle); with your own server, use ```ts import { createServer } from 'node:http'; -import { createAgent, mountChannels, resolveAgentDir } from 'lousho'; +import { createAgent, mountChannels, resolveAgentDir } from '@linuxdevil/lousho'; // channels/support.ts: export default webhookChannel({ secret: process.env.HOOK_SECRET ?? '' }) const { config, channels } = await resolveAgentDir('./my-agent'); @@ -259,7 +259,7 @@ naming the file. A `memory` override passed to `loadAgentDir(dir, { overrides }) is merged with the directory's slots by name: the override wins a name clash. ```ts -import { defineMemory, fileMemory, resolveAgentDir } from 'lousho'; +import { defineMemory, fileMemory, resolveAgentDir } from '@linuxdevil/lousho'; // memory/notes.ts: export default { scope: 'global', provider: fileMemory({ dir: './.lousho/memory' }) } const { manifest } = await resolveAgentDir('./my-agent', { @@ -273,7 +273,7 @@ A relative `dir` in a `memory/*.ts` file resolves against the process working di ```ts // memory/notes.ts import { fileURLToPath } from 'node:url'; -import { fileMemory } from 'lousho'; +import { fileMemory } from '@linuxdevil/lousho'; export default { scope: 'global', @@ -297,7 +297,7 @@ The path follows the module: a copied or bundled build keeps its memory next to handy for tests and tooling: ```ts -import { resolveAgentDir, createAgent } from 'lousho'; +import { resolveAgentDir, createAgent } from '@linuxdevil/lousho'; const { config, manifest } = await resolveAgentDir('./my-agent'); console.log(manifest.tools, manifest.skills, manifest.subagents); @@ -317,8 +317,8 @@ declares (`guardrails`, `onEvent`, `retry`, `reasoning`, `redactContent`, `output`, ...). Use it to swap the model in tests: ```ts no-run -import { loadAgentDir } from 'lousho'; -import { mockModel } from 'lousho/testing'; +import { loadAgentDir } from '@linuxdevil/lousho'; +import { mockModel } from '@linuxdevil/lousho/testing'; const agent = await loadAgentDir('./my-agent', { provider: mockModel(['Hi there.']) }); ``` @@ -349,7 +349,7 @@ says so. Compile the directory first, or use `.js`/`.mjs` tools with an ## Run it with `lousho dev` ```bash -npx lousho dev ./my-agent +npx @linuxdevil/lousho dev ./my-agent ``` Serves the chat UI and `POST /chat` for the directory and reloads it when @@ -368,7 +368,7 @@ dev](schedules.md#in-lousho-dev)). ## Deploy it with `lousho build` ```bash -npx lousho build ./my-agent --target=node-server # or docker +npx @linuxdevil/lousho build ./my-agent --target=node-server # or docker ``` The agent directory is the unit of deployment: the built server loads it with diff --git a/docs/agent-forge.md b/docs/agent-forge.md index 4606a8019..40914539a 100644 --- a/docs/agent-forge.md +++ b/docs/agent-forge.md @@ -24,7 +24,7 @@ Agent Forge ships inside `lousho` itself - there's no separate package to install: ```bash -npm install lousho +npm install @linuxdevil/lousho ``` That's it. `lousho studio` (below) runs a pre-built copy of the app; you @@ -37,7 +37,7 @@ Agent Forge itself), see [Dev mode](#dev-mode) below. ## Quickstart ```bash -npx lousho studio +npx @linuxdevil/lousho studio ``` This starts one local server and prints its URL (default @@ -63,10 +63,10 @@ actions** (`⋯`) menu. Useful flags: ```bash -npx lousho studio --port 5000 # pick a different port -npx lousho studio --host 0.0.0.0 # bind to all interfaces -npx lousho studio --prod # force production mode (see below) -npx lousho studio --dev # force dev mode (monorepo only) +npx @linuxdevil/lousho studio --port 5000 # pick a different port +npx @linuxdevil/lousho studio --host 0.0.0.0 # bind to all interfaces +npx @linuxdevil/lousho studio --prod # force production mode (see below) +npx @linuxdevil/lousho studio --dev # force dev mode (monorepo only) ``` ### Access token @@ -596,7 +596,7 @@ and deploy cards; see [Settings, secrets and deploy wiring](#settings-secrets-an `.lousho/agents//traces//.jsonl` under the directory `lousho studio` was started in (see [Local traces](observability.md#local-traces)): - `npx lousho traces --dir .lousho/agents//traces` shows the same + `npx @linuxdevil/lousho traces --dir .lousho/agents//traces` shows the same runs in the terminal, and deleting the agent's folder deletes its traces. The tab shows what the spans hold: model and tool names, token counts, cost, durations and error messages, and in the inspector's Input, @@ -828,7 +828,7 @@ yet: git clone https://github.com/LinuxDevil/lousho.git cd agent-sdk npm install -npx lousho studio --dev # or just `npx lousho studio` before building +npx @linuxdevil/lousho studio --dev # or just `npx @linuxdevil/lousho studio` before building ``` This runs the API server straight from TypeScript (`tsx`) and a real Vite @@ -840,7 +840,7 @@ is pinned to port 5173 and the API server accepts that origin. To build the prod ```bash npm run build:studio # builds apps/agent-forge/dist (client) and # apps/agent-forge/dist-server (bundled server) -npx lousho studio # now runs in production mode +npx @linuxdevil/lousho studio # now runs in production mode ``` `npm run build:studio` also runs automatically as part of the root diff --git a/docs/agent-spec.md b/docs/agent-spec.md index 973a65feb..a39e8b2a2 100644 --- a/docs/agent-spec.md +++ b/docs/agent-spec.md @@ -74,8 +74,8 @@ A minimal spec is four fields. This snippet parses one and runs it on the built-in `mock` provider, so it works offline with no API key: ```ts -import { createMockProvider, LLMProviderRegistry, specV2ToAgent } from 'lousho'; -import { parseAgentSpecV2 } from 'lousho/spec'; +import { createMockProvider, LLMProviderRegistry, specV2ToAgent } from '@linuxdevil/lousho'; +import { parseAgentSpecV2 } from '@linuxdevil/lousho/spec'; // 'mock' is a registered provider name; scripting its reply keeps the output deterministic. LLMProviderRegistry.register('mock', () => createMockProvider({ responses: ['Hello!'] })); @@ -274,8 +274,8 @@ for when you want to build the agent yourself. v1's `specToAgent()` stays separate: it is synchronous and takes a v1 spec, not v2 data. ```ts -import { compileSpec, specV2ToAgent, type SpecHost } from 'lousho'; -import { parseAgentSpecV2 } from 'lousho/spec'; +import { compileSpec, specV2ToAgent, type SpecHost } from '@linuxdevil/lousho'; +import { parseAgentSpecV2 } from '@linuxdevil/lousho/spec'; const host: SpecHost = { baseDir: process.cwd() }; const spec = parseAgentSpecV2({ version: 2, name: 'Greeter', model: 'mock/greeter', instructions: 'Say hello.' }); @@ -303,14 +303,14 @@ and v1's `agentSpecSchema`. Node-only helpers (`specV2ToAgent`, stay on the package root. ```ts -import { agentSpecV2JsonSchema } from 'lousho/spec'; +import { agentSpecV2JsonSchema } from '@linuxdevil/lousho/spec'; const schema = agentSpecV2JsonSchema(); // the draft-07 JSON Schema of a spec, for forms and editors console.log(schema.title); // 'AgentSpec v2' ``` ```ts -import { parseAgentSpecV2, specToCode } from 'lousho/spec'; +import { parseAgentSpecV2, specToCode } from '@linuxdevil/lousho/spec'; const spec = parseAgentSpecV2({ version: 2, name: 'Greeter', model: 'mock/greeter', instructions: 'Say hello.' }); console.log(specToCode(spec)); // the same spec as a readable createAgent({...}) module @@ -325,7 +325,7 @@ is not re-validated - run it through `parseAgentSpecV2()` or use version and always returns a validated `AgentSpecV2`. ```ts -import { loadSpecV2, upgradeSpec } from 'lousho'; +import { loadSpecV2, upgradeSpec } from '@linuxdevil/lousho'; const v1 = { name: 'support-bot', diff --git a/docs/ai-sdk-ui.md b/docs/ai-sdk-ui.md index 45a309764..6f5719182 100644 --- a/docs/ai-sdk-ui.md +++ b/docs/ai-sdk-ui.md @@ -18,7 +18,7 @@ The `vendor/` model prefix chooses the provider; with OpenRouter use `openrouter//` (e.g. `openrouter/openai/gpt-4o-mini`). ```ts -import { createAgent, fromUIMessages, memoryStore, toUIMessageStreamResponse, type UIMessageLike } from 'lousho'; +import { createAgent, fromUIMessages, memoryStore, toUIMessageStreamResponse, type UIMessageLike } from '@linuxdevil/lousho'; // The store must outlive the request: without one, every `agent.session({ id })` // gets a fresh in-memory transcript and the next turn would start empty. @@ -155,5 +155,5 @@ kept: a `system` message (or any other role, or an entry that is not a message) is dropped, so a caller cannot add instructions to the agent's prompt. Set instructions on the agent (`createAgent({ instructions })`), not in the request. -A dedicated `lousho/ai-sdk-ui` subpath may follow; for now the +A dedicated `@linuxdevil/lousho/ai-sdk-ui` subpath may follow; for now the functions come from the package root. diff --git a/docs/api-overview.md b/docs/api-overview.md index ac41b769a..bbfe2e48a 100644 --- a/docs/api-overview.md +++ b/docs/api-overview.md @@ -44,9 +44,9 @@ How the pieces fit: | Export | Description | | ----------------------------- | --------------------------------------------------------------------------- | | `createAgent(config)` | Zero-config `{ send(message) }` agent from a `model` string or provider (+ instructions, tools). | -| `AgentBuilder` (from `lousho/executor`) | Fluent builder for an `AgentConfig` (`AgentBuilder.create().setName(...)...build()`). Advanced: see [the executor API](./executor-api.md). | -| `AgentExecutor.execute(opts)` (from `lousho/executor`) | Static executor: runs an agent (LLM + tool-calling loop) and resolves to an `ExecutionResult`. Advanced: see [the executor API](./executor-api.md). | -| `resumeAfterApproval()` (from `lousho/executor`) | Resume an execution paused for human approval. Advanced: see [the executor API](./executor-api.md). | +| `AgentBuilder` (from `@linuxdevil/lousho/executor`) | Fluent builder for an `AgentConfig` (`AgentBuilder.create().setName(...)...build()`). Advanced: see [the executor API](./executor-api.md). | +| `AgentExecutor.execute(opts)` (from `@linuxdevil/lousho/executor`) | Static executor: runs an agent (LLM + tool-calling loop) and resolves to an `ExecutionResult`. Advanced: see [the executor API](./executor-api.md). | +| `resumeAfterApproval()` (from `@linuxdevil/lousho/executor`) | Resume an execution paused for human approval. Advanced: see [the executor API](./executor-api.md). | | `InMemoryApprovalStore` | Process-local `ApprovalStore`; the default store of `createAgent()` agents. | | `StorageServiceApprovalStore`, `LocalStorageCheckpointStore` (from `/utils`) | File-backed approval and checkpoint stores over a `StorageService` (see [Approvals](./approvals.md), [Durable execution](./durable-execution.md)). | | `SqliteStore` (from `/sqlite`) | Sessions, checkpoints and approvals in one SQLite file (see [Sessions](./sessions.md#choosing-a-store)). | @@ -74,8 +74,8 @@ resolves with the task prompt as `input` and the lead run's `sessionId`, `metadata` and `principal`. ```ts -import { createAgent, defineTool } from 'lousho'; -import { mockModel } from 'lousho/testing'; +import { createAgent, defineTool } from '@linuxdevil/lousho'; +import { mockModel } from '@linuxdevil/lousho/testing'; import { z } from 'zod'; const refund = defineTool({ name: 'refund', description: 'Refunds an order', input: z.object({ orderId: z.string() }), execute: async () => 'ok' }); @@ -104,14 +104,14 @@ when it is created. ### UI bindings -`lousho/react` exports `useLoushoAgent(source, options?)`, a +`@linuxdevil/lousho/react` exports `useLoushoAgent(source, options?)`, a React hook that runs an agent in process (`{ agent, sessionId? }`) or over HTTP (`{ url }`) and returns `messages`, `status`, `pendingApproval`, `send()`, `stop()`, `approve()` and `reject()`. Its framework-neutral parts, `reduceAgentEvents()` and `parseEventStream()`, are exported too. See -[React](./react.md). `lousho/vue` exports the same +[React](./react.md). `@linuxdevil/lousho/vue` exports the same `useLoushoAgent()` as a Vue 3 composable, with the state as refs. See -[Vue](./vue.md). `lousho/svelte` exports `loushoAgent()`, the +[Vue](./vue.md). `@linuxdevil/lousho/svelte` exports `loushoAgent()`, the same state and actions as a Svelte store (`$agent`). See [Svelte](./svelte.md). ### Sub-agents @@ -209,7 +209,7 @@ See [Providers](./providers.md) for how a model string is resolved and which mod ## Testing -Exported from `lousho/testing` (see [Testing agents](testing.md)). +Exported from `@linuxdevil/lousho/testing` (see [Testing agents](testing.md)). | Export | Description | | --------------------- | ---------------------------------------------------------------------------------------- | @@ -222,18 +222,18 @@ Exported from `lousho/testing` (see [Testing agents](testing.md)). | -------------------------------------------- | --------------------------------------------- | | `defineTool({ name, description, input, execute, ... })` | Define a tool; `execute`/`needsApproval` args are inferred from the zod `input`. Accepted by `createAgent({ tools: [...] })`, `ToolRegistry.register(tool)` and `AgentBuilder.addTool(tool)`. | | `ToolInput`, `ToolOutput` | Argument and result types of a defined tool. | -| `ToolRegistry` (from `lousho/tools` or `lousho/executor`) | Holds the tools an agent config refers to (advanced: `register(tool)` or `register(name, descriptor)`). | +| `ToolRegistry` (from `@linuxdevil/lousho/tools` or `@linuxdevil/lousho/executor`) | Holds the tools an agent config refers to (advanced: `register(tool)` or `register(name, descriptor)`). | | `httpTool`, `currentDateTool`, `dayNameTool` | Built-in tools. | | `createFsTools(fs, options?)`, `createShellTool(shell, options?)` | Workspace tools (`read_file`, `write_file`, `edit_file`, `list_dir`, `glob`, `grep`, `shell`) over an `FsProvider` / `ShellProvider`. See [Workspace tools](workspace-tools.md). | | `NodeWorkspace`, `MemoryWorkspace`, `SandboxShell` | Workspace providers: a real directory (paths confined to `root`, minimal shell env), an in-memory tree with a scripted `exec` for tests, and a `ShellProvider` over a `SandboxAdapter` (Docker). | | `createTodoTools({ store?, onChange? })` | `todo_write` / `todo_read` tools (plus `getTodos()`) so agents can plan and track multi-step work; see [Todo tools](./tools.md#todo-tools). | | `connectMcp(servers, options?)` | Connect MCP servers from config (stdio or HTTP) and load their tools; see [Share servers with `connectMcp()`](./mcp.md#share-servers-with-connectmcp). | -| `loadMcpTools(client, connectionName)` | Load a connected MCP server's tools as `ToolDescriptor`s. Available from the package root, `lousho/tools`, and `lousho/mcp`. | +| `loadMcpTools(client, connectionName)` | Load a connected MCP server's tools as `ToolDescriptor`s. Available from the package root, `@linuxdevil/lousho/tools`, and `@linuxdevil/lousho/mcp`. | See [Tools](./tools.md) for a guide to defining and registering tools. ```ts -import { defineTool } from 'lousho'; +import { defineTool } from '@linuxdevil/lousho'; import { z } from 'zod'; const sendEmail = defineTool({ @@ -259,7 +259,7 @@ execute }`) is legacy, still built for compatibility, and used only for descriptors that do not set `inputSchema` / `execute`. ```ts -import { loadMcpTools } from 'lousho/mcp'; +import { loadMcpTools } from '@linuxdevil/lousho/mcp'; const tools = await loadMcpTools(mcpClient, 'my-server'); ``` @@ -286,7 +286,7 @@ warning naming the server, the tool and the reason, and `onSkip` to collect what was left out: ```ts -import { loadMcpTools, type SkippedMcpTool } from 'lousho/mcp'; +import { loadMcpTools, type SkippedMcpTool } from '@linuxdevil/lousho/mcp'; const skipped: SkippedMcpTool[] = []; const tools = await loadMcpTools(mcpClient, 'my-server', { @@ -315,13 +315,13 @@ Token estimates, the model price table, and the usage and cost of a run: see [Mo ## Flows, evals, observability and security -- `FlowBuilder` / `FlowExecutor` (`lousho/flows`) - multi-step +- `FlowBuilder` / `FlowExecutor` (`@linuxdevil/lousho/flows`) - multi-step workflow graphs; see [Flows](./flows.md). -- `TriggerAdapter` and `TriggerRegistry` (`lousho/triggers`) - +- `TriggerAdapter` and `TriggerRegistry` (`@linuxdevil/lousho/triggers`) - the extension point for your own wake-up mechanism, plus `verifySlackSignature()`, `parseCronExpression()` and the `WebhookAuth` helpers; see [Triggers](./triggers.md). - `jwt()`, `oidc()`, `basic()`, `apiToken()`, `anonymous()` and `routeAuth()` - (`lousho/auth`) - route auth as an ordered list; the caller + (`@linuxdevil/lousho/auth`) - route auth as an ordered list; the caller it accepts reaches the run as `principal`; see [Route auth and principals](./auth.md). - `defineEval()`, scorers such as `exactMatch`, `toolCallOrder` and `budget`, checks such as `includes` and `atLeast`, and `llmJudge()` - agent evals run under vitest @@ -329,7 +329,7 @@ Token estimates, the model price table, and the usage and cost of a run: see [Mo - `withSpan()` and `TraceExporter` - tracing for `AgentExecutor.execute()`. `TraceExporter` is a bring-your-own-exporter interface (no exporter ships by default); for real OpenTelemetry spans, import - `createOtelTraceExporter()` from the `lousho/otel` + `createOtelTraceExporter()` from the `@linuxdevil/lousho/otel` subpath (requires the optional peer dependency `@opentelemetry/api`) instead of hand-rolling the OTel bridge - see `examples/tracing/run-otel.ts`. Spans follow the OpenTelemetry GenAI @@ -343,7 +343,7 @@ Token estimates, the model price table, and the usage and cost of a run: see [Mo `GenerateHookContext` - hooks that observe, deny, rewrite or redact tool calls and model calls. See [Hooks](./hooks.md). - `EncryptionUtils`, `sha256`, `StorageService` - (`lousho/utils`) - supporting utilities; see + (`@linuxdevil/lousho/utils`) - supporting utilities; see [Utilities](./utilities.md). ## Deployment diff --git a/docs/approvals.md b/docs/approvals.md index 01de151e3..3c6874dc8 100644 --- a/docs/approvals.md +++ b/docs/approvals.md @@ -10,7 +10,7 @@ See also: [Pause, restart, resume](./pause-and-resume.md), a short end-to-end tu > Snippets on this page leave some names to you: `provider` is any `LLMProvider` (offline: `createMockProvider()`); `agent` is a `createAgent()` result; `input` is the user's text, a `string`; `emailTool` is a tool built with `defineTool()`; `storage` is a `StorageService`; `toolRegistry` is a `ToolRegistry`. ```ts -import { createAgent, defineTool } from 'lousho'; +import { createAgent, defineTool } from '@linuxdevil/lousho'; import { z } from 'zod'; const sendEmail = defineTool({ @@ -36,7 +36,7 @@ if (paused.finishReason === 'awaiting-approval') { arguments, typed from the tool's zod `input`: ```ts -import { defineTool } from 'lousho'; +import { defineTool } from '@linuxdevil/lousho'; import { z } from 'zod'; const sendEmail = defineTool({ @@ -89,7 +89,7 @@ error. `allow(tools)`, `deny(tools, reason?)` and `ask(tools)` build the common rules. ```ts -import { allow, ask, createAgent, defineTool, deny, type PermissionRule } from 'lousho'; +import { allow, ask, createAgent, defineTool, deny, type PermissionRule } from '@linuxdevil/lousho'; import { z } from 'zod'; const shell = defineTool({ @@ -168,7 +168,7 @@ session, each `send()` is its own transcript. Compacting away that message makes the tool ask again. ```ts -import { createAgent, defineTool, once } from 'lousho'; +import { createAgent, defineTool, once } from '@linuxdevil/lousho'; import { z } from 'zod'; const askOnce = once(); @@ -234,8 +234,8 @@ restart, give the agent a durable store, such as the SQLite one (see [Choosing a store](./sessions.md#choosing-a-store)): ```ts -import { createAgent } from 'lousho'; -import { SqliteStore } from 'lousho/sqlite'; +import { createAgent } from '@linuxdevil/lousho'; +import { SqliteStore } from '@linuxdevil/lousho/sqlite'; const store = new SqliteStore('./.lousho/agent.db'); const agent = createAgent({ provider, tools: [emailTool], store }); // or approvalStore: store.approvals @@ -269,7 +269,7 @@ The step's calls that need no approval do not wait: they run when the step does, before the pause. ```ts -import { createAgent } from 'lousho'; +import { createAgent } from '@linuxdevil/lousho'; const agent = createAgent({ model: 'openai/gpt-4o-mini' /* , tools: [payInvoice, archiveThread] */ }); const paused = await agent.send('Pay the invoice and archive the thread'); @@ -311,7 +311,7 @@ decided. `approved: false`. ```ts -import { createAgent } from 'lousho'; +import { createAgent } from '@linuxdevil/lousho'; const agent = createAgent({ model: 'openai/gpt-4o-mini', tools: [emailTool] }); const support = agent.session({ id: 'support-42' }); @@ -338,7 +338,7 @@ approval. There is no option on `resolve()` that ends the run. To stop that: model gets the reason and answers: ```ts -import { createAgent, type AgentHook } from 'lousho'; +import { createAgent, type AgentHook } from '@linuxdevil/lousho'; const noRefile: AgentHook = { name: 'no-refile', @@ -369,7 +369,7 @@ is only on the audit entry (`onPermissionDecision`). An expired pause resolved with `approved: true` does not run the tool. ```ts -import { createAgent, defineTool } from 'lousho'; +import { createAgent, defineTool } from '@linuxdevil/lousho'; import { z } from 'zod'; const sendEmail = defineTool({ @@ -436,7 +436,7 @@ Its argument's `principal` is whose call it is, and tool's `ctx.approval.by` (see [auth](./auth.md#who-approved)). ```ts -import { createAgent } from 'lousho'; +import { createAgent } from '@linuxdevil/lousho'; const trusted = createAgent({ provider, @@ -454,10 +454,10 @@ score, a timeout, a model error, arguments too long to judge — defers to a human exactly like `'defer'`. It never denies a call on its own. ```ts -import { ask, createAgent, createAutoApprove, modelAutoApprover } from 'lousho'; +import { ask, createAgent, createAutoApprove, modelAutoApprover } from '@linuxdevil/lousho'; declare const issueTool: any; -declare const cheapProvider: import('lousho').LLMProvider; +declare const cheapProvider: import('@linuxdevil/lousho').LLMProvider; const agent = createAgent({ provider, @@ -492,11 +492,11 @@ permissions: ``` ```ts -import { modelAutoApprover, specV2ToAgent } from 'lousho'; +import { modelAutoApprover, specV2ToAgent } from '@linuxdevil/lousho'; -declare const spec: import('lousho').AgentSpecV2; +declare const spec: import('@linuxdevil/lousho').AgentSpecV2; declare const baseDir: string; -declare const cheapProvider: import('lousho').LLMProvider; +declare const cheapProvider: import('@linuxdevil/lousho').LLMProvider; const agent = await specV2ToAgent(spec, { baseDir, @@ -523,7 +523,7 @@ is what `resolve()` resolves with. A continuation that pauses again ends with continues in that session, and `run.done` comes once the transcript is saved. ```ts -import { createAgent } from 'lousho'; +import { createAgent } from '@linuxdevil/lousho'; const agent = createAgent({ provider, tools: [emailTool] }); const paused = await agent.send('Email Sam the report'); @@ -560,7 +560,7 @@ approval: in sessions, in durable stores and across a restart. in tests and scripted agents. ```ts -import { createAgent } from 'lousho'; +import { createAgent } from '@linuxdevil/lousho'; const agent = createAgent({ instructions: 'Plan the trip with the user.', provider, askQuestion: true }); @@ -597,8 +597,8 @@ tool. Resume later, after a real restart if you like, with `resumeAfterApproval()`: ```ts -import { AgentExecutor, resumeAfterApproval } from 'lousho/executor'; -import { StorageServiceApprovalStore } from 'lousho/utils'; +import { AgentExecutor, resumeAfterApproval } from '@linuxdevil/lousho/executor'; +import { StorageServiceApprovalStore } from '@linuxdevil/lousho/utils'; const approvalStore = new StorageServiceApprovalStore(storage); diff --git a/docs/auth.md b/docs/auth.md index af3a7081a..d38d9023e 100644 --- a/docs/auth.md +++ b/docs/auth.md @@ -1,7 +1,7 @@ # Route auth and principals An agent served over HTTP needs to know two things about each request: may it -call at all, and who is it. `lousho/auth` answers both. You +call at all, and who is it. `@linuxdevil/lousho/auth` answers both. You give a route an ordered list of auth entries (`jwt()`, `oidc()`, `basic()`, `apiToken()`, `anonymous()` or your own function); the first entry that accepts the request returns a **principal**, and that principal goes into the @@ -9,9 +9,9 @@ run, where your `model` / `instructions` / `tools` functions and memory scopes can read it. ```ts no-run -import { createAgent, createRouteHandler } from 'lousho'; -import { apiToken, jwt } from 'lousho/auth'; -import { mockModel } from 'lousho/testing'; +import { createAgent, createRouteHandler } from '@linuxdevil/lousho'; +import { apiToken, jwt } from '@linuxdevil/lousho/auth'; +import { mockModel } from '@linuxdevil/lousho/testing'; const agent = createAgent({ provider: mockModel(['Hello!']), @@ -83,7 +83,7 @@ exactly one key source: | `jwksUrl` | a JWKS endpoint (https, or http on localhost) | `RS256`, `ES256` by default; RSA and EC keys only | ```ts -import { jwt } from 'lousho/auth'; +import { jwt } from '@linuxdevil/lousho/auth'; const fromOurAuthServer = jwt({ jwksUrl: 'https://auth.example.com/.well-known/jwks.json', @@ -123,7 +123,7 @@ or http on `localhost` / `127.0.0.1` / `[::1]` — the same rule as `jwt()`'s `jwksUrl`). Algorithms default to `RS256` and `ES256`; HMAC is not allowed. ```ts -import { oidc } from 'lousho/auth'; +import { oidc } from '@linuxdevil/lousho/auth'; const google = oidc({ issuer: 'https://accounts.google.com', audience: process.env.GOOGLE_CLIENT_ID ?? 'client-id' }); ``` @@ -139,7 +139,7 @@ carries `WWW-Authenticate: Basic realm="", charset="UTF-8"` so browsers show their login prompt. ```ts -import { basic } from 'lousho/auth'; +import { basic } from '@linuxdevil/lousho/auth'; const operators = basic({ users: { ops: process.env.OPS_PASSWORD! }, realm: 'agent' }); // Or check against your own store (compare in constant time there): @@ -168,7 +168,7 @@ Any function of the `Request` is an entry. Declare the challenges it answers a `401` with, or it advertises `Bearer`: ```ts -import { AuthError, type AuthFn } from 'lousho/auth'; +import { AuthError, type AuthFn } from '@linuxdevil/lousho/auth'; const tenantKey: AuthFn = async (request) => { const key = request.headers.get('x-tenant-key'); @@ -228,7 +228,7 @@ In an [agent directory](./agent-directories.md), put the list in `auth.ts` (or ```ts no-verify // my-agent/auth.ts -import { apiToken, oidc } from 'lousho/auth'; +import { apiToken, oidc } from '@linuxdevil/lousho/auth'; export default [ oidc({ issuer: 'https://login.example.com', audience: 'agent-api' }), @@ -258,8 +258,8 @@ The principal is on the run context next to `sessionId` and `metadata`. The `openrouter//` (e.g. `openrouter/openai/gpt-4o-mini`). ```ts -import { createAgent, defineMemory, inMemoryMemory, type Principal } from 'lousho'; -import { mockModel } from 'lousho/testing'; +import { createAgent, defineMemory, inMemoryMemory, type Principal } from '@linuxdevil/lousho'; +import { mockModel } from '@linuxdevil/lousho/testing'; const notes = defineMemory({ name: 'notes', @@ -343,7 +343,7 @@ none in any of these places. ### In a tool ```ts -import { defineTool } from 'lousho'; +import { defineTool } from '@linuxdevil/lousho'; import { z } from 'zod'; export const myOrders = defineTool({ @@ -364,8 +364,8 @@ from the request body or the model, and a tool cannot change it. ### Approval policies and permission rules ```ts -import { createAgent, defineTool } from 'lousho'; -import { mockModel } from 'lousho/testing'; +import { createAgent, defineTool } from '@linuxdevil/lousho'; +import { mockModel } from '@linuxdevil/lousho/testing'; import { z } from 'zod'; const deploy = defineTool({ @@ -393,8 +393,8 @@ A decision can name who made it. The approved tool sees the decider as `ctx.approval.by`, next to `ctx.approval.note`: ```ts -import { createAgent, defineTool, type Principal } from 'lousho'; -import { mockModel } from 'lousho/testing'; +import { createAgent, defineTool, type Principal } from '@linuxdevil/lousho'; +import { mockModel } from '@linuxdevil/lousho/testing'; import { z } from 'zod'; const sendEmail = defineTool({ @@ -464,8 +464,8 @@ see the example under [Who may decide an approval](#who-may-decide-an-approval)) caller keeps the check stateless: ```ts -import { createAgent, createRouteHandler, memoryStore } from 'lousho'; -import { jwt } from 'lousho/auth'; +import { createAgent, createRouteHandler, memoryStore } from '@linuxdevil/lousho'; +import { jwt } from '@linuxdevil/lousho/auth'; const agent = createAgent({ model: 'openai/gpt-4o-mini', store: memoryStore() }); // The user id a session id starts with: everything before its last `-`. A prefix match would let @@ -521,7 +521,7 @@ Behind a reverse proxy: rewrites `Host`, list the upstream name the proxy sends, not the public one. ```ts -import { createAgent, createRouteHandler } from 'lousho'; +import { createAgent, createRouteHandler } from '@linuxdevil/lousho'; const agent = createAgent({ model: 'openai/gpt-4o-mini' }); @@ -551,8 +551,8 @@ customer can no longer approve their own refund, and a supervisor can read any session (to see what they are approving) and decide in it: ```ts -import { createAgent, createRouteHandler, memoryStore } from 'lousho'; -import { jwt } from 'lousho/auth'; +import { createAgent, createRouteHandler, memoryStore } from '@linuxdevil/lousho'; +import { jwt } from '@linuxdevil/lousho/auth'; const agent = createAgent({ model: 'openai/gpt-4o-mini', store: memoryStore() }); const isSupervisor = (principal?: { claims?: Readonly> }) => principal?.claims?.role === 'supervisor'; @@ -574,8 +574,8 @@ claims, a manager decides them, and nobody approves their own: ```ts no-verify // my-agent/auth.ts -import { apiToken } from 'lousho/auth'; -import type { ApprovalAccessRequest, SessionAccessRequest } from 'lousho'; +import { apiToken } from '@linuxdevil/lousho/auth'; +import type { ApprovalAccessRequest, SessionAccessRequest } from '@linuxdevil/lousho'; export default [ apiToken(process.env.ALICE_TOKEN!, { id: 'alice' }), diff --git a/docs/build-a-coding-agent.md b/docs/build-a-coding-agent.md index 8a09bcf50..08387b791 100644 --- a/docs/build-a-coding-agent.md +++ b/docs/build-a-coding-agent.md @@ -16,7 +16,7 @@ file system and a shell for it, and `createFsTools()` and `createShellTool()` turn it into tools. ```ts -import { NodeWorkspace, createFsTools, createShellTool } from 'lousho'; +import { NodeWorkspace, createFsTools, createShellTool } from '@linuxdevil/lousho'; const workspace = new NodeWorkspace({ root: process.cwd() }); const tools = [...createFsTools(workspace), createShellTool(workspace)]; @@ -41,7 +41,7 @@ per-tool map. For the shell, pass a predicate over the command string and gate only the commands you consider risky. ```ts -import { NodeWorkspace, createFsTools, createShellTool } from 'lousho'; +import { NodeWorkspace, createFsTools, createShellTool } from '@linuxdevil/lousho'; const DESTRUCTIVE = /\b(rm|mv|chmod|chown|git\s+(push|reset|clean|checkout))\b|>/; @@ -73,7 +73,7 @@ written and `tool.start` announces each tool call (see [Streaming](./streaming.md)). ```ts -import { NodeWorkspace, createAgent, createFsTools, createShellTool } from 'lousho'; +import { NodeWorkspace, createAgent, createFsTools, createShellTool } from '@linuxdevil/lousho'; const workspace = new NodeWorkspace({ root: process.cwd() }); const agent = createAgent({ @@ -110,7 +110,7 @@ the rest of the work. That run can pause again, so loop until there is no ```ts import { createInterface } from 'node:readline/promises'; -import { createAgent, type AgentRun } from 'lousho'; +import { createAgent, type AgentRun } from '@linuxdevil/lousho'; const agent = createAgent({ model: 'openai/gpt-4o-mini', instructions: 'You are a coding agent.' }); const rl = createInterface({ input: process.stdin, output: process.stdout }); @@ -165,7 +165,7 @@ same pause handling. // coding.ts // Run: OPENAI_API_KEY=... npx tsx coding.ts "Fix the failing test" import { createInterface } from 'node:readline/promises'; -import { NodeWorkspace, createAgent, createFsTools, createShellTool, type AgentRun } from 'lousho'; +import { NodeWorkspace, createAgent, createFsTools, createShellTool, type AgentRun } from '@linuxdevil/lousho'; const DESTRUCTIVE = /\b(rm|mv|chmod|chown|git\s+(push|reset|clean|checkout))\b|>/; @@ -249,8 +249,8 @@ memory and takes a scripted `exec` for the shell, and `mockModel` plays the model. Assert on `workspace.snapshot()` and `workspace.commands`. ```ts -import { createAgent, createFsTools, createShellTool, MemoryWorkspace } from 'lousho'; -import { mockModel } from 'lousho/testing'; +import { createAgent, createFsTools, createShellTool, MemoryWorkspace } from '@linuxdevil/lousho'; +import { mockModel } from '@linuxdevil/lousho/testing'; const workspace = new MemoryWorkspace({ files: { 'math.js': 'exports.add = (a, b) => a - b;\n' }, @@ -277,8 +277,8 @@ To test the approval path, gate `write_file` the way the real agent does. runs the write and finishes the turn. ```ts -import { createAgent, createFsTools, MemoryWorkspace } from 'lousho'; -import { mockModel } from 'lousho/testing'; +import { createAgent, createFsTools, MemoryWorkspace } from '@linuxdevil/lousho'; +import { mockModel } from '@linuxdevil/lousho/testing'; const workspace = new MemoryWorkspace({ files: { 'notes.txt': 'old\n' } }); const agent = createAgent({ diff --git a/docs/channels.md b/docs/channels.md index 4302b3577..98b916d20 100644 --- a/docs/channels.md +++ b/docs/channels.md @@ -43,7 +43,7 @@ const channels = mountFetchChannels(agent, [slack], { waitUntil: (turn) => void ```ts no-run import * as http from 'node:http'; -import { createAgent, createMockProvider, httpChannel, mountChannels } from 'lousho'; +import { createAgent, createMockProvider, httpChannel, mountChannels } from '@linuxdevil/lousho'; const agent = createAgent({ instructions: 'You are helpful.', provider: createMockProvider() }); const channels = mountChannels(agent, [ @@ -118,7 +118,7 @@ Discord, Telegram, Teams, GitHub) keeps its own answer and tells the user the request failed in the conversation. ```ts -import { createAgent, defineChannel, mountChannels } from 'lousho'; +import { createAgent, defineChannel, mountChannels } from '@linuxdevil/lousho'; const agent = createAgent({ instructions: 'You answer text messages.', provider }); @@ -202,7 +202,7 @@ dropped silently once the surface was acknowledged. `resolveApproval()` throws `LOUSHO_APPROVAL_CONFLICT` for an approval another request is deciding. ```ts -import { createAgent, defineChannel, mountChannels } from 'lousho'; +import { createAgent, defineChannel, mountChannels } from '@linuxdevil/lousho'; const agent = createAgent({ instructions: 'You are helpful.', provider, askQuestion: true }); @@ -247,7 +247,7 @@ the verified request (the alerting system that sent the hook, an integration user); `body` is the parsed JSON body, `undefined` when it is not JSON. ```ts -import { createAgent, httpChannel, mountChannels, webhookChannel } from 'lousho'; +import { createAgent, httpChannel, mountChannels, webhookChannel } from '@linuxdevil/lousho'; const agent = createAgent({ instructions: 'You are helpful.', provider }); @@ -340,7 +340,7 @@ Set up the app at [api.slack.com/apps](https://api.slack.com/apps): ```ts import * as http from 'node:http'; -import { createAgent, mountChannels, slackChannel } from 'lousho'; +import { createAgent, mountChannels, slackChannel } from '@linuxdevil/lousho'; const agent = createAgent({ instructions: 'You are a helpful Slack bot.', provider }); @@ -415,7 +415,7 @@ curl -X POST "https://discord.com/api/v10/applications/$APP_ID/guilds/$GUILD_ID/ ```ts import * as http from 'node:http'; -import { createAgent, discordChannel, mountChannels } from 'lousho'; +import { createAgent, discordChannel, mountChannels } from '@linuxdevil/lousho'; const agent = createAgent({ instructions: 'You are a helpful Discord bot.', provider }); @@ -499,7 +499,7 @@ curl -X POST "https://api.telegram.org/bot$TELEGRAM_BOT_TOKEN/setWebhook" -H "Co ```ts import * as http from 'node:http'; -import { createAgent, mountChannels, telegramChannel } from 'lousho'; +import { createAgent, mountChannels, telegramChannel } from '@linuxdevil/lousho'; const agent = createAgent({ instructions: 'You are a helpful Telegram bot.', provider }); @@ -551,7 +551,7 @@ so it also runs on Workers. them somewhere shared and durable, e.g. Redis `SET NX EX`: ```ts - import { githubChannel, type DeliveryStore } from 'lousho'; + import { githubChannel, type DeliveryStore } from '@linuxdevil/lousho'; const seenIds = new Set(); // stands in for Redis or a database table const deliveries: DeliveryStore = { seen: (id) => seenIds.has(id) || (seenIds.add(id), false) }; @@ -652,7 +652,7 @@ agent from answering its own comments. ```ts import * as http from 'node:http'; -import { createAgent, githubChannel, mountChannels } from 'lousho'; +import { createAgent, githubChannel, mountChannels } from '@linuxdevil/lousho'; const agent = createAgent({ instructions: 'You answer questions about this repository.', provider }); @@ -748,7 +748,7 @@ Set up the bot: ```ts import * as http from 'node:http'; -import { createAgent, mountChannels, teamsChannel } from 'lousho'; +import { createAgent, mountChannels, teamsChannel } from '@linuxdevil/lousho'; const agent = createAgent({ instructions: 'You help the team with questions.', provider }); diff --git a/docs/claude-projects.md b/docs/claude-projects.md index c2712c403..19d2084e0 100644 --- a/docs/claude-projects.md +++ b/docs/claude-projects.md @@ -7,8 +7,8 @@ into `createAgent()`, so the same directory drives both harnesses. Nothing is copied or renamed; Lousho reads the files where they already are. ```ts no-run -import { createAgent, claudeProject } from 'lousho'; -import { mockModel } from 'lousho/testing'; +import { createAgent, claudeProject } from '@linuxdevil/lousho'; +import { mockModel } from '@linuxdevil/lousho/testing'; const provider = mockModel([{ text: 'ok' }]); @@ -65,8 +65,8 @@ task-shaped notes, hooks can pause on `scratchpad` writes like any tool. ## Options ```ts no-run -import { claudeProject } from 'lousho'; -import { mockModel } from 'lousho/testing'; +import { claudeProject } from '@linuxdevil/lousho'; +import { mockModel } from '@linuxdevil/lousho/testing'; const provider = mockModel([{ text: 'ok' }]); diff --git a/docs/cloudflare-workers.md b/docs/cloudflare-workers.md index e9db8a126..261d84a03 100644 --- a/docs/cloudflare-workers.md +++ b/docs/cloudflare-workers.md @@ -1,6 +1,6 @@ # Cloudflare Workers -`npx lousho build --target=cloudflare-worker --agent=agent.yaml` generates a module Worker (`worker.ts`), a `wrangler.toml` and a bundle, `dist/worker.js`, that the build checks for `node:` imports. It needs `tsup` and `wrangler` installed, and it takes an [agent spec file](./configuration.md) or an [agent directory](./agent-directories.md) (`npx lousho build ./my-agent --target=cloudflare-worker`). The Worker serves the same [HTTP API](./deployment.md#http-api) as the other targets. This target has the tightest limits of the three, so they come first. +`npx @linuxdevil/lousho build --target=cloudflare-worker --agent=agent.yaml` generates a module Worker (`worker.ts`), a `wrangler.toml` and a bundle, `dist/worker.js`, that the build checks for `node:` imports. It needs `tsup` and `wrangler` installed, and it takes an [agent spec file](./configuration.md) or an [agent directory](./agent-directories.md) (`npx @linuxdevil/lousho build ./my-agent --target=cloudflare-worker`). The Worker serves the same [HTTP API](./deployment.md#http-api) as the other targets. This target has the tightest limits of the three, so they come first. ## What works and what does not @@ -12,7 +12,7 @@ | [Agent directories](./agent-directories.md) | yes: config, `instructions.md`, `tools/`, `skills/`, `subagents/`, `schedules/` (Worker cron rules), `channels/` (mounted under `/channels`), `memory/` (`kvMemory()`, or `inMemoryMemory()` per isolate) and `projectInstructions` (embedded at build time) | yes, all of it | | Providers | `mock`, `openai`, `anthropic`, `openrouter` | `mock`, `openai`, `anthropic`, `ollama`, `openrouter` | | Built-in tools | `current-date`, `day-name`, `http` (listed host names only, from the `LOUSHO_HTTP_ALLOW` binding) | all, including `http` and `web-fetch` | -| Tools written in TypeScript | yes, in an agent directory: no Node builtins, and from `lousho` only the names listed under [Agent directories on a Worker](#build-and-deploy) | yes, in an agent directory | +| Tools written in TypeScript | yes, in an agent directory: no Node builtins, and from `@linuxdevil/lousho` only the names listed under [Agent directories on a Worker](#build-and-deploy) | yes, in an agent directory | | Sandboxed tool execution | no (the build swaps the sandbox for a shim that fails when used) | yes | | MCP servers over stdio | no (the child process cannot start; the shim fails when used) | yes | | [Code mode](./code-mode.md) (`codeMode`) | no (the build swaps QuickJS for a shim that fails the run at its start) | yes | @@ -105,7 +105,7 @@ Worker builds the agent with the same rules as `resolveAgentDir()` (config keys, tool exports, duplicate tool names): ```bash -npx lousho build ./my-agent --target=cloudflare-worker +npx @linuxdevil/lousho build ./my-agent --target=cloudflare-worker cd .lousho/build/cloudflare-worker && npx wrangler deploy ``` @@ -211,8 +211,8 @@ binding typed as `KVBinding` (the `get`/`put`/`delete` part of Cloudflare's from the `/worker` subpath - see [Writing your own Worker](#writing-your-own-worker): ```ts -import { createAgent } from 'lousho/worker'; -import { KVStore, type KVBinding } from 'lousho/kv'; +import { createAgent } from '@linuxdevil/lousho/worker'; +import { KVStore, type KVBinding } from '@linuxdevil/lousho/kv'; interface Env { AGENT_KV: KVBinding; @@ -275,7 +275,7 @@ new call, which gets a new approval id, so a tool that must act once per invoice also needs a key from its own arguments (the invoice number here): ```ts -import { defineTool } from 'lousho'; +import { defineTool } from '@linuxdevil/lousho'; import { z } from 'zod'; // `payments` stands in for your payment client. @@ -317,7 +317,7 @@ day-of-week (`LOUSHO_SCHEDULE_INVALID`). In a Worker you write yourself, wire an agent defined in code with `handleScheduled(agent, schedules, controller, ctx)` (exported from -`lousho/worker` — the `lousho/deploy-runtime-worker` +`@linuxdevil/lousho/worker` — the `@linuxdevil/lousho/deploy-runtime-worker` specifier in the generated code is a virtual one that only `lousho build`'s bundler resolves; do not import it yourself). It runs the schedules whose `cron` equals `controller.cron` inside `ctx.waitUntil()` and never throws; list @@ -326,9 +326,9 @@ the same expressions under `[triggers] crons` yourself. Give the agent a store isolate's memory and is gone when it is recycled: ```ts -import { createAgent, createMockProvider, defineSchedule, handleScheduled } from 'lousho/worker'; -import type { ScheduledContext, ScheduledController } from 'lousho/worker'; -import { KVStore, type KVBinding } from 'lousho/kv'; +import { createAgent, createMockProvider, defineSchedule, handleScheduled } from '@linuxdevil/lousho/worker'; +import type { ScheduledContext, ScheduledController } from '@linuxdevil/lousho/worker'; +import { KVStore, type KVBinding } from '@linuxdevil/lousho/kv'; interface Env { AGENT_CHECKPOINTS: KVBinding; @@ -350,7 +350,7 @@ export default { ## Writing your own Worker -Import from `lousho/worker`, not the package root. The root entry +Import from `@linuxdevil/lousho/worker`, not the package root. The root entry also exports file stores, the agent-directory loader, MCP over stdio, sandboxes and other Node-only modules, so bundling it for `platform: 'browser'` fails with `Could not resolve "async_hooks"` and friends. `/worker` is `createAgent` plus the @@ -362,8 +362,8 @@ import { createAgent, createMockProvider, defineSchedule, defineTool, handleScheduled, memoryStore, serveFetch, OpenAIProvider, AnthropicProvider, OpenRouterProvider, LLMProviderRegistry, SDKError, /* ... */ -} from 'lousho/worker'; -import { KVStore } from 'lousho/kv'; // also re-exported from /worker +} from '@linuxdevil/lousho/worker'; +import { KVStore } from '@linuxdevil/lousho/kv'; // also re-exported from /worker ``` The exports: `createAgent` (and its config types), `memoryStore`, the KV stores @@ -436,8 +436,8 @@ read-modify-write is not atomic. ## Bundle size and Node builtins The KV-backed stores (`KVStore`, `KVCheckpointStore` and `CHECKPOINT_KV_BINDING`, -exported from `lousho/kv`) have no `node:*` references anywhere -in their dependency graph, and neither does the `lousho/worker` +exported from `@linuxdevil/lousho/kv`) have no `node:*` references anywhere +in their dependency graph, and neither does the `@linuxdevil/lousho/worker` entry a hand-written Worker bundles (its build is checked the same way; see [Writing your own Worker](#writing-your-own-worker)). The Worker runs the spec as a `createAgent()` agent, whose Node-only imports (project instructions, the file session store, guardrail diff --git a/docs/code-mode.md b/docs/code-mode.md index cf579bd78..9371564d0 100644 --- a/docs/code-mode.md +++ b/docs/code-mode.md @@ -35,7 +35,7 @@ npm install quickjs-emscripten@^0.32.0 ``` ```ts -import { createAgent, defineTool } from 'lousho'; +import { createAgent, defineTool } from '@linuxdevil/lousho'; import { z } from 'zod'; const prices: Record = { apple: 1.2, pear: 0.8, plum: 2 }; diff --git a/docs/compaction.md b/docs/compaction.md index 7bc52c931..e657942c0 100644 --- a/docs/compaction.md +++ b/docs/compaction.md @@ -18,7 +18,7 @@ OpenRouter use `openrouter//` (e.g. `openrouter/openai/gpt-4o-mini`). ```ts -import { createAgent } from 'lousho'; +import { createAgent } from '@linuxdevil/lousho'; const simple = createAgent({ model: 'openai/gpt-4o', compaction: true }); @@ -84,7 +84,7 @@ listeners (`createAgent({ onEvent })`, `onAgentEvent`) or is streamed. a cheap summarizer model: ```ts -import { createAgent, createCompactionHook, twoPhaseStrategy } from 'lousho'; +import { createAgent, createCompactionHook, twoPhaseStrategy } from '@linuxdevil/lousho'; const agent = createAgent({ provider, @@ -157,7 +157,7 @@ model's loaded context length (`loaded_context_length` from prints the model's default). ```ts -import { createAgent, registerModel } from 'lousho'; +import { createAgent, registerModel } from '@linuxdevil/lousho'; // Either on the agent... const agent = createAgent({ @@ -183,8 +183,8 @@ Registering the hook in a `HookRegistry` is for code that calls the executor directly (see [the executor API](./executor-api.md)): ```ts -import { AgentExecutor } from 'lousho/executor'; -import { HookRegistry, createCompactionHook, twoPhaseStrategy } from 'lousho'; +import { AgentExecutor } from '@linuxdevil/lousho/executor'; +import { HookRegistry, createCompactionHook, twoPhaseStrategy } from '@linuxdevil/lousho'; const hooks = new HookRegistry(); hooks.register( @@ -270,7 +270,7 @@ message; a pinned tool result keeps its whole assistant turn. Pin what the agent must always see in full, such as the task statement or a key document: ```ts -import { pinMessage, isPinned, type Message } from 'lousho'; +import { pinMessage, isPinned, type Message } from '@linuxdevil/lousho'; const messages: Message[] = [ { role: 'system', content: 'You are a research assistant.' }, @@ -288,7 +288,7 @@ of the conversation, and resolves to a new array (its input is not modified). Use it to shrink a stored transcript before you continue it: ```ts -import { compactMessages, summarizeStrategy, type Message } from 'lousho'; +import { compactMessages, summarizeStrategy, type Message } from '@linuxdevil/lousho'; declare const history: Message[]; @@ -322,8 +322,8 @@ durable turn is unfinished. Listeners added with `session.on()` get checkpoint and checkpoint history, and emits `context.cleared`. ```ts -import { createAgent, twoPhaseStrategy } from 'lousho'; -import { mockModel } from 'lousho/testing'; +import { createAgent, twoPhaseStrategy } from '@linuxdevil/lousho'; +import { mockModel } from '@linuxdevil/lousho/testing'; const agent = createAgent({ provider: mockModel(['ok']) }); const session = agent.session({ compaction: { strategy: twoPhaseStrategy({ model: 'openai/gpt-4o-mini' }) } }); @@ -348,7 +348,7 @@ unchanged when there is nothing to do: the hook then leaves the run alone and does not call `onCompaction`. ```ts -import { createCompactionHook, isPinned, type CompactionStrategy } from 'lousho'; +import { createCompactionHook, isPinned, type CompactionStrategy } from '@linuxdevil/lousho'; // Keep the system prompt, pinned messages and the last 20 messages. const keepRecent: CompactionStrategy = { diff --git a/docs/configuration.md b/docs/configuration.md index 2d0f6d3c0..c6c0bf86f 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -142,7 +142,7 @@ mcpServers: ``` ```ts -import { agentSpecSchema, specToAgent, type McpServerSpec } from 'lousho'; +import { agentSpecSchema, specToAgent, type McpServerSpec } from '@linuxdevil/lousho'; const filesystem: McpServerSpec = { command: 'npx', args: ['-y', '@modelcontextprotocol/server-filesystem'] }; const spec = agentSpecSchema.parse({ @@ -185,7 +185,7 @@ other models. The `vendor/` model prefix chooses the provider; with OpenRouter use `openrouter//` (e.g. `openrouter/openai/gpt-4o-mini`). ```ts -import { createAgent } from 'lousho'; +import { createAgent } from '@linuxdevil/lousho'; const agent = createAgent({ model: 'openai/gpt-4o-mini', @@ -227,7 +227,7 @@ To build the same thing by hand, or for providers you construct yourself, anywhere a provider is accepted: ```ts -import { createAgent, resolveProvider, withFallback, withRetry } from 'lousho'; +import { createAgent, resolveProvider, withFallback, withRetry } from '@linuxdevil/lousho'; const provider = withFallback( [ @@ -298,7 +298,7 @@ flow waves and eval suites cannot burst unbounded model calls. Every limit is optional: ```ts -import { withRateLimit, resolveProvider } from 'lousho'; +import { withRateLimit, resolveProvider } from '@linuxdevil/lousho'; const provider = withRateLimit(resolveProvider('openai/gpt-4o-mini'), { requestsPerMinute: 60, // calls started per rolling minute @@ -328,7 +328,7 @@ its own provider is queued too). Give several agents the same `createRateLimiter()` to share it across them. ```ts -import { createAgent } from 'lousho'; +import { createAgent } from '@linuxdevil/lousho'; const researcher = createAgent({ model: 'openai/gpt-4o-mini', instructions: 'You research.', description: 'Finds sources' }); createAgent({ model: 'openai/gpt-4o-mini', subagents: { researcher }, rateLimit: { maxConcurrent: 2 } }); @@ -407,7 +407,7 @@ A `send()` or `stream()` call can pass its own, which win key by key over the agent's: ```ts -import { createAgent } from 'lousho'; +import { createAgent } from '@linuxdevil/lousho'; const agent = createAgent({ model: 'openai/gpt-4o-mini', @@ -442,7 +442,7 @@ Besides `modelSettings`, a `send()` or `stream()` call can set its own instructions for that run: ```ts -import { createAgent } from 'lousho'; +import { createAgent } from '@linuxdevil/lousho'; const agent = createAgent({ model: 'openai/gpt-4o-mini', instructions: 'You triage support tickets.' }); await agent.send('Summarize the ticket.', { maxSteps: 2, instructions: 'Answer in French.' }); @@ -460,7 +460,7 @@ keeps `model` / `provider` and `instructions` / `prompt` mutually exclusive, so TypeScript rejects spreading it next to a literal that sets one of them. ```ts -import { createAgent, type CreateAgentBase } from 'lousho'; +import { createAgent, type CreateAgentBase } from '@linuxdevil/lousho'; const shared: CreateAgentBase = { maxSteps: 8, modelSettings: { temperature: 0 } }; @@ -519,7 +519,7 @@ with a checkpoint store it is checkpointed as finished, like `'max-steps'`. (`LOUSHO_BUDGET_EXCEEDED`, with the same `budget`) instead. ```ts -import { BudgetExceededError, createAgent } from 'lousho'; +import { BudgetExceededError, createAgent } from '@linuxdevil/lousho'; const agent = createAgent({ provider, @@ -548,7 +548,7 @@ continued from its store keeps its budget. Both apply together; the first limit reached stops the turn. A turn that was aborted is not counted. ```ts -import { createAgent, memoryStore } from 'lousho'; +import { createAgent, memoryStore } from '@linuxdevil/lousho'; const agent = createAgent({ provider, store: memoryStore(), limits: { maxTokens: 20_000 } }); const session = agent.session({ id: 'user-42', limits: { maxCostUsd: 1 } }); @@ -562,7 +562,7 @@ Many repositories keep guidance for coding agents in an `AGENTS.md` (or `CLAUDE.md`) file. `createAgent` can append it to the agent's instructions: ```ts -import { createAgent } from 'lousho'; +import { createAgent } from '@linuxdevil/lousho'; const agent = createAgent({ instructions: 'You review pull requests.', diff --git a/docs/connectors.md b/docs/connectors.md index 127f68347..7072fede8 100644 --- a/docs/connectors.md +++ b/docs/connectors.md @@ -6,7 +6,7 @@ connector is an **MCP server**: add it to `mcpServers` and its tools join the agent's registry namespaced as `__`. ```ts no-run -import { createAgent } from 'lousho'; +import { createAgent } from '@linuxdevil/lousho'; const agent = createAgent({ model: 'openrouter/openai/gpt-4o-mini', @@ -47,8 +47,8 @@ into the SDK's approval gate: a `slack.post_message` pauses the run until approved, `docs.get_document` does not. Tune it per server: ```ts no-run -import { createAgent } from 'lousho'; -import { mockModel } from 'lousho/testing'; +import { createAgent } from '@linuxdevil/lousho'; +import { mockModel } from '@linuxdevil/lousho/testing'; const provider = mockModel([{ text: 'ok' }]); diff --git a/docs/decisions.md b/docs/decisions.md index 00a7d83ab..51be42df9 100644 --- a/docs/decisions.md +++ b/docs/decisions.md @@ -5,7 +5,7 @@ Public beta: `gpt-6-luna` is the only model the endpoint serves, and the surface may still change. The endpoint is not part of chat completions, so it does not go through `createAgent()` providers or OpenRouter — it needs `OPENAI_API_KEY` (or `apiKey`/`baseURL` for a compatible endpoint). ```ts -import { decide } from 'lousho'; +import { decide } from '@linuxdevil/lousho'; const { answers } = await decide({ input: 'I was charged twice for my order.', @@ -32,7 +32,7 @@ if (route.type === 'choice' && route.confidence >= 0.7) { ## Question types ```ts no-run -import { decide, type DecisionQuestion } from 'lousho'; +import { decide, type DecisionQuestion } from '@linuxdevil/lousho'; const questions: DecisionQuestion[] = [ // predicate: probability 0-1 that a condition holds @@ -69,7 +69,7 @@ const questions: DecisionQuestion[] = [ Independent questions share one `input` and one request: ```ts no-run -import { decide, type DecisionQuestion } from 'lousho'; +import { decide, type DecisionQuestion } from '@linuxdevil/lousho'; const questions: DecisionQuestion[] = [ { type: 'predicate', name: 'is_spam', instructions: 'Is this spam?' }, @@ -91,7 +91,7 @@ const byName = Object.fromEntries(answers.map((a) => [a.name, a])); ## Options ```ts no-run -import { decide } from 'lousho'; +import { decide } from '@linuxdevil/lousho'; const result = await decide({ input: '...', diff --git a/docs/deployment.md b/docs/deployment.md index 987585752..c215d4825 100644 --- a/docs/deployment.md +++ b/docs/deployment.md @@ -4,7 +4,7 @@ into a deployable artifact for one target platform: ```bash -npx lousho build --target= --agent=agent.yaml [--out=] +npx @linuxdevil/lousho build --target= --agent=agent.yaml [--out=] ``` It runs the target's adapter through three steps - **scaffold** (write the @@ -30,7 +30,7 @@ All three targets also take an [agent directory](./agent-directories.md) in place of a spec file (the path may be positional or `--agent`): ```bash -npx lousho build ./my-agent --target=node-server +npx @linuxdevil/lousho build ./my-agent --target=node-server node .lousho/build/node-server/dist/server.js ``` @@ -41,7 +41,7 @@ directory's [schedules](./schedules.md) when it listens, mounts its routes keep the bearer token), and logs `schedules: ...; channels: ...`. The directory's code files are pre-bundled with the build's tsup step into `dist/agent/**.js` (one ESM build sharing one copy of the SDK, so a `from -'lousho'` import in a tool is the SDK the server runs), and +'@linuxdevil/lousho'` import in a tool is the SDK the server runs), and only the files the server reads as data are copied: `instructions.md`, `instructions/`, `skills/`, a JSON/YAML config, and the same in each `subagents//`. Nothing else in the directory reaches `dist/`: not `.env` @@ -62,7 +62,7 @@ only a Worker-safe part of `lousho` ([Cloudflare Workers](./cloudflare-workers.md#build-and-deploy) has the details): ```bash -npx lousho build ./my-agent --target=cloudflare-worker +npx @linuxdevil/lousho build ./my-agent --target=cloudflare-worker ``` ## Agent modules @@ -73,7 +73,7 @@ same file `lousho dev src/agent.ts` serves, and what the minimal and tools scaffolds have (their `npm run build` runs this): ```bash -npx lousho build src/agent.ts --target=node-server +npx @linuxdevil/lousho build src/agent.ts --target=node-server node .lousho/build/node-server/dist/server.js ``` @@ -291,7 +291,7 @@ signal handler: stop accepting, let the requests in flight finish, then call ```ts import { createServer } from 'node:http'; -import { createAgent, mountChannels } from 'lousho'; +import { createAgent, mountChannels } from '@linuxdevil/lousho'; const agent = createAgent({ model: 'openai/gpt-4o-mini' }); const handler = mountChannels(agent, []); diff --git a/docs/durable-execution.md b/docs/durable-execution.md index 3fce903a6..626c206b3 100644 --- a/docs/durable-execution.md +++ b/docs/durable-execution.md @@ -10,8 +10,8 @@ and any `ApprovalStore` work the same way. See also: [Pause, restart, resume](./pause-and-resume.md), a short end-to-end tutorial. ```ts -import { createAgent } from 'lousho'; -import { SqliteStore } from 'lousho/sqlite'; +import { createAgent } from '@linuxdevil/lousho'; +import { SqliteStore } from '@linuxdevil/lousho/sqlite'; const agent = createAgent({ provider, store: new SqliteStore('./.lousho/jobs.db') }); @@ -104,7 +104,7 @@ session (the session already has its system prompt). To end a session and start over under the same id, call `store.checkpoints.delete(sessionId)`. ```ts -import { createAgent, memoryStore, SessionAwaitingApprovalError } from 'lousho'; +import { createAgent, memoryStore, SessionAwaitingApprovalError } from '@linuxdevil/lousho'; const agent = createAgent({ provider, store: memoryStore() }); @@ -136,7 +136,7 @@ underneath): 3. calls the model once every call of the turn has exactly one result. ```ts -import { createAgent, memoryStore } from 'lousho'; +import { createAgent, memoryStore } from '@linuxdevil/lousho'; const agent = createAgent({ provider, store: memoryStore() }); @@ -160,7 +160,7 @@ finishes it. `agent.pending()` lists those runs across every session and `send(msg, { sessionId })` id, so a restarted process can find them: ```ts -import { createAgent, fileStore } from 'lousho'; +import { createAgent, fileStore } from '@linuxdevil/lousho'; const agent = createAgent({ provider, store: fileStore('./.lousho') }); @@ -225,8 +225,8 @@ without durable writes) is listed as `'in-progress'` with `lastError.code` to the code that serves traffic: ```ts -import { createAgent, defineTool, memoryStore, PropagatingToolError } from 'lousho'; -import { mockModel } from 'lousho/testing'; +import { createAgent, defineTool, memoryStore, PropagatingToolError } from '@linuxdevil/lousho'; +import { mockModel } from '@linuxdevil/lousho/testing'; import { z } from 'zod'; let charges = 0; @@ -436,7 +436,7 @@ model calls). To run a job once, derive the `sessionId` from the delivery id and look at the checkpoint before sending: ```ts -import { createAgent, memoryStore } from 'lousho'; +import { createAgent, memoryStore } from '@linuxdevil/lousho'; const store = memoryStore(); const agent = createAgent({ provider, store }); @@ -474,7 +474,7 @@ same call is re-run on resume) is passed to `execute`, so it works as an idempotency key against a re-run of one call: ```ts -import { defineTool } from 'lousho'; +import { defineTool } from '@linuxdevil/lousho'; import { z } from 'zod'; declare const payments: { charge(amount: number, opts: { idempotencyKey: string }): Promise<{ id: string }> }; @@ -510,7 +510,7 @@ one invoice) needs an idempotency key from your own data, such as the claim id in the arguments, checked or passed on to the system you write to: ```ts -import { defineTool } from 'lousho'; +import { defineTool } from '@linuxdevil/lousho'; import { z } from 'zod'; declare const claims: { file(claim: { claimId: string }, opts: { idempotencyKey: string }): Promise<{ id: string }> }; @@ -540,14 +540,14 @@ Which stores keep one: | `SqliteStore` | yes (`checkpoint_history` table) | `new SqliteStore(path, { historyLimit })` | | `LocalStorageCheckpointStore` | yes | `new LocalStorageCheckpointStore(storage, { historyLimit })` | | `fileStore(dir)` (JSON files) | yes (`checkpoint-history/.json`) | `fileStore(dir, { historyLimit })` | -| `KVCheckpointStore` / `KVStore` (Cloudflare Workers KV, from `lousho/kv`) | yes (see below) | `new KVStore(kv, { historyLimit })` | +| `KVCheckpointStore` / `KVStore` (Cloudflare Workers KV, from `@linuxdevil/lousho/kv`) | yes (see below) | `new KVStore(kv, { historyLimit })` | | Agent Forge's `FileCheckpointStore` | yes | `new FileCheckpointStore(dir, { historyLimit })` | | a custom `CheckpointStore` | only if it implements `history()` | - | For example, with the in-memory store: ```ts -import { getCheckpointHistory, memoryStore } from 'lousho'; +import { getCheckpointHistory, memoryStore } from '@linuxdevil/lousho'; const { checkpoints } = memoryStore({ historyLimit: 20 }); // default 50, 0 keeps none // new SqliteStore(path, { historyLimit }), new LocalStorageCheckpointStore(storage, { historyLimit }) @@ -602,7 +602,7 @@ saves the result as the `'in-progress'` checkpoint of the new session over fork like any unfinished run with `agent.resume(fork.sessionId)`: ```ts -import { createAgent, compareTrajectories, memoryStore } from 'lousho'; +import { createAgent, compareTrajectories, memoryStore } from '@linuxdevil/lousho'; const store = memoryStore(); const assistant = createAgent({ provider, store }); @@ -683,8 +683,8 @@ it and it has no result yet, or the human approved it) always rejects with whatever the option. ```ts -import { createAgent, memoryStore } from 'lousho'; -import type { LLMProvider } from 'lousho'; +import { createAgent, memoryStore } from '@linuxdevil/lousho'; +import type { LLMProvider } from '@linuxdevil/lousho'; declare const provider: LLMProvider; @@ -725,8 +725,8 @@ call it corresponds to. `agent.send(message, { sessionId })`: ```ts -import { AgentExecutor } from 'lousho/executor'; -import { LocalStorageCheckpointStore } from 'lousho/utils'; +import { AgentExecutor } from '@linuxdevil/lousho/executor'; +import { LocalStorageCheckpointStore } from '@linuxdevil/lousho/utils'; const checkpoints = new LocalStorageCheckpointStore(storage); @@ -740,8 +740,8 @@ await AgentExecutor.execute({ agent, input: 'Make it 3 people', provider, sessio It throws `SessionAwaitingApprovalError` on a paused session, as `send()` does: ```ts -import { AgentExecutor } from 'lousho/executor'; -import { SessionAwaitingApprovalError, memoryStore } from 'lousho'; +import { AgentExecutor } from '@linuxdevil/lousho/executor'; +import { SessionAwaitingApprovalError, memoryStore } from '@linuxdevil/lousho'; const { checkpoints } = memoryStore(); @@ -758,7 +758,7 @@ The exported `resumeAfterApproval()` function is what `agent.approvals.resolve()` calls: ```ts -import { AgentExecutor, resumeAfterApproval } from 'lousho/executor'; +import { AgentExecutor, resumeAfterApproval } from '@linuxdevil/lousho/executor'; const paused = await AgentExecutor.execute({ agent, input, provider, toolRegistry, approvalStore, sessionId: 'chat-42', checkpointStore, @@ -782,8 +782,8 @@ if (paused.finishReason === 'awaiting-approval') { fork's `sessionId` is `agent.resume(fork.sessionId)`: ```ts -import { AgentExecutor } from 'lousho/executor'; -import { compareTrajectories, memoryStore } from 'lousho'; +import { AgentExecutor } from '@linuxdevil/lousho/executor'; +import { compareTrajectories, memoryStore } from '@linuxdevil/lousho'; const { checkpoints } = memoryStore(); await AgentExecutor.execute({ agent, provider, toolRegistry, input: 'Plan my trip', sessionId: 'trip', checkpointStore: checkpoints }); diff --git a/docs/errors.md b/docs/errors.md index 0b868ff87..9d019cfcd 100644 --- a/docs/errors.md +++ b/docs/errors.md @@ -31,7 +31,7 @@ serialising the error is unchanged). It is absent when the run failed before it started. ```ts -import { createAgent, type RunUsage } from 'lousho'; +import { createAgent, type RunUsage } from '@linuxdevil/lousho'; try { await createAgent({ provider }).send('Go'); @@ -52,7 +52,7 @@ on purpose: `PropagatingToolError` and its subclasses (a tool throws one to stop the run), `WorkspaceError` (a workspace operation refused or failed - the model sees it as a tool error), `McpToolError` (a loaded MCP tool whose server reports `isError`; also a tool error), `AuthError` (an -`lousho/auth` entry rejects the request with its 401 or 403), +`@linuxdevil/lousho/auth` entry rejects the request with its 401 or 403), `CronExpressionError` (`parseCronExpression()`, which `defineSchedule()` wraps as `LOUSHO_SCHEDULE_INVALID`) and `DecryptionError` (`EncryptionUtils`, `DTOEncryptionFilter`). @@ -63,7 +63,7 @@ server reports `isError`; also a tool error), `AuthError` (an and `.cassette` / `.callNumber` fields. ```ts -import { createAgent, SDKError } from 'lousho'; +import { createAgent, SDKError } from '@linuxdevil/lousho'; try { await createAgent({ provider, instructions: 'Be brief.' }).send('hi'); @@ -400,7 +400,7 @@ where the agent is being built. agent spec has no field for. **Fix:** build the agent with `createAgent()` and pass the configured tool, e.g. -`createGitHubTools(config)` from `lousho/integrations`. +`createGitHubTools(config)` from `@linuxdevil/lousho/integrations`. **Example:** `tools: [github]` in a spec. @@ -1257,7 +1257,7 @@ dataset and add examples to it (`POST /api/v1/datasets`, `POST **Means:** a record/replay cassette is missing, is not valid JSON or does not match the recorded request. The message names the file. A mismatch inside a run reaches `send()` as the `CassetteMismatchError` itself (from -`lousho/testing`, with `.cassette` and `.callNumber`) - it is +`@linuxdevil/lousho/testing`, with `.cassette` and `.callNumber`) - it is a test-fixture failure, so it is never compacted into a `CompactedLLMProviderError`. @@ -1560,7 +1560,7 @@ keep reaching the run that holds the id. ### LOUSHO_AUTH_CONFIG_INVALID -**Means:** a route auth helper from `lousho/auth` (`jwt()`, +**Means:** a route auth helper from `@linuxdevil/lousho/auth` (`jwt()`, `oidc()`, `basic()`, `apiToken()`) got options it cannot verify with, and refused them when it was created, not at the first request. The message names the option: no key source or two of them, an HMAC `secret` shorter than 32 diff --git a/docs/evals.md b/docs/evals.md index d6da59702..a1f9b8c8c 100644 --- a/docs/evals.md +++ b/docs/evals.md @@ -14,7 +14,7 @@ prints a summary and writes JUnit and JSON reports. ```bash npm install --save-dev vitest -npx lousho eval +npx @linuxdevil/lousho eval ``` ## Writing a trajectory eval @@ -28,8 +28,8 @@ for the [judge evals](#judge-evals) below. ```ts no-run // refund.eval.ts import { z } from 'zod'; -import { createAgent, defineEval, defineTool, includes } from 'lousho'; -import { mockModel } from 'lousho/testing'; +import { createAgent, defineEval, defineTool, includes } from '@linuxdevil/lousho'; +import { mockModel } from '@linuxdevil/lousho/testing'; const lookupOrder = defineTool({ name: 'lookup_order', @@ -111,7 +111,7 @@ it. Pass `{ subagent: 'researcher' }` to look at one sub-agent (and the sub-agents under it) only: ```ts -import { createAgent, defineEval } from 'lousho'; +import { createAgent, defineEval } from '@linuxdevil/lousho'; const researcher = createAgent({ model: 'openai/gpt-4o-mini', instructions: 'You research.', description: 'Finds sources' }); const agent = createAgent({ model: 'openai/gpt-4o-mini', subagents: { researcher } }); @@ -141,7 +141,7 @@ case `n` times; the case passes when all `n` runs pass, or when at least `passAt: k` of them do: ```ts -import { createAgent, defineEval } from 'lousho'; +import { createAgent, defineEval } from '@linuxdevil/lousho'; const agent = createAgent({ model: 'openai/gpt-4o-mini', instructions: 'You handle refunds.' }); @@ -167,8 +167,8 @@ any object; its `label` (or `name`, or its `input`) names it. ```ts no-run import { z } from 'zod'; -import { createAgent, defineEval, defineTool } from 'lousho'; -import { mockModel } from 'lousho/testing'; +import { createAgent, defineEval, defineTool } from '@linuxdevil/lousho'; +import { mockModel } from '@linuxdevil/lousho/testing'; const lookupOrder = defineTool({ name: 'lookup_order', @@ -220,7 +220,7 @@ curl -X POST "$LOUSHO_OBSERVE/api/v1/datasets/refunds/examples" -H 'content-type ``` ```ts no-run -import { createAgent, defineEval, includes } from 'lousho'; +import { createAgent, defineEval, includes } from '@linuxdevil/lousho'; defineEval({ name: 'refunds', @@ -258,8 +258,8 @@ it. ```ts no-run // tone.judge.eval.ts: run with `lousho eval --judge` -import { createAgent, defineEval, atLeast } from 'lousho'; -import { mockModel } from 'lousho/testing'; +import { createAgent, defineEval, atLeast } from '@linuxdevil/lousho'; +import { mockModel } from '@linuxdevil/lousho/testing'; defineEval({ name: 'tone', @@ -338,7 +338,7 @@ model. `--timeout ms` sets the limit for every case (`0` turns it off). With One eval can set its own limit, which wins over both: ```ts -import { createAgent, defineEval } from 'lousho'; +import { createAgent, defineEval } from '@linuxdevil/lousho'; defineEval({ name: 'research report', @@ -379,7 +379,7 @@ jobs: - uses: actions/setup-node@v4 with: { node-version: 22 } - run: npm ci - - run: npx lousho eval --junit reports/evals.xml --json reports/evals.json + - run: npx @linuxdevil/lousho eval --junit reports/evals.xml --json reports/evals.json - uses: actions/upload-artifact@v4 if: always() with: @@ -420,9 +420,9 @@ between: it records each case once against the real provider through Your eval files do not change. ```bash -npx lousho eval --record # real provider: writes the cassettes, commit them -npx lousho eval --replay # every case from its cassette, no network, no key -npx lousho eval --drift # re-record and diff each case's trajectory +npx @linuxdevil/lousho eval --record # real provider: writes the cassettes, commit them +npx @linuxdevil/lousho eval --replay # every case from its cassette, no network, no key +npx @linuxdevil/lousho eval --drift # re-record and diff each case's trajectory ``` - **`--record`** runs every case with its agent's real provider and writes one @@ -475,7 +475,7 @@ a real judge only in `*.judge.eval.ts` files. How it works: the run loop sends every model call (`generate` or `stream`, from a plain run, a streamed run, a sub-agent or an approval resume) through one provider-interception seam, `setProviderInterceptor()` (exported from -`lousho/testing`; `src/providers/interception.ts` in the +`@linuxdevil/lousho/testing`; `src/providers/interception.ts` in the source). Nothing is installed by default, so the seam is free. While a case runs under one of these modes, `lousho eval` answers it with the provider wrapped by `recordReplay()` for that case's cassette. You can use @@ -493,7 +493,7 @@ The same eval file that gates CI in-process can smoke-test a deployed agent (the URL and every case runs against the deployment instead of the in-process agent: ```bash -npx lousho eval --url https://agent.example.com --token "$DEPLOY_TOKEN" # or LOUSHO_EVAL_TOKEN +npx @linuxdevil/lousho eval --url https://agent.example.com --token "$DEPLOY_TOKEN" # or LOUSHO_EVAL_TOKEN ``` Each case gets its own remote session (`POST /chat { sessionId, input }`, one @@ -519,7 +519,7 @@ and tools the behaviour you want to check. In code, pass a `target` (this overrides `agent`; `--url` overrides both): ```ts -import { defineEval, remoteTarget } from 'lousho'; +import { defineEval, remoteTarget } from '@linuxdevil/lousho'; defineEval({ name: 'deployed refund flow', diff --git a/docs/executor-api.md b/docs/executor-api.md index 5958368e0..5463e4d9f 100644 --- a/docs/executor-api.md +++ b/docs/executor-api.md @@ -15,8 +15,8 @@ take: `temperature` and `maxTokens` (`exporter`, `captureContent` and import { AgentBuilder, AgentExecutor, -} from 'lousho/executor'; -import { createMockProvider } from 'lousho'; +} from '@linuxdevil/lousho/executor'; +import { createMockProvider } from '@linuxdevil/lousho'; const agent = AgentBuilder.create() .setName('Customer Support Agent') diff --git a/docs/flows.md b/docs/flows.md index ced5f1eea..6843b11b7 100644 --- a/docs/flows.md +++ b/docs/flows.md @@ -6,8 +6,8 @@ Describe the graph with `FlowBuilder` and run it with the static `FlowExecutor.execute(flow, context, onEvent?)`. ```ts -import { FlowBuilder, FlowExecutor, type EditorStep } from 'lousho/flows'; -import { resolveProvider, type AgentConfig } from 'lousho'; +import { FlowBuilder, FlowExecutor, type EditorStep } from '@linuxdevil/lousho/flows'; +import { resolveProvider, type AgentConfig } from '@linuxdevil/lousho'; // A flow calls an LLMProvider directly (reads OPENAI_API_KEY). const provider = resolveProvider('openai/gpt-4o-mini'); @@ -109,7 +109,7 @@ its tool runs: never run unapproved. ```ts -import { FlowExecutor, type AgentFlow } from 'lousho/flows'; +import { FlowExecutor, type AgentFlow } from '@linuxdevil/lousho/flows'; declare const flow: AgentFlow; @@ -134,7 +134,7 @@ set stays as before: `'{{missing}}'` is `''`, and `'$USD'` (no variable named `USD`) is the literal string `'$USD'`. ```ts -import type { EditorStep } from 'lousho/flows'; +import type { EditorStep } from '@linuxdevil/lousho/flows'; // With variables { count: 5, tags: ['a', 'b'] }, the tool gets { count: 5, tags: ['a', 'b'], note: 'count: 5' }. const step: EditorStep = { @@ -156,7 +156,7 @@ once it is aborted, no further step starts and the flow fails with the signal's to the model request, and a `toolCall` hands it to the tool as `ctx.abortSignal`. ```ts -import { FlowExecutor, type AgentFlow } from 'lousho/flows'; +import { FlowExecutor, type AgentFlow } from '@linuxdevil/lousho/flows'; declare const flow: AgentFlow; @@ -214,7 +214,7 @@ Invalid values (`maxAttempts` < 1 or not an integer, a negative `backoffMs`, a `timeoutMs` <= 0) fail the step with `LOUSHO_FLOW_INVALID`. ```ts -import type { EditorStep } from 'lousho/flows'; +import type { EditorStep } from '@linuxdevil/lousho/flows'; const fetchInvoice: EditorStep = { type: 'toolCall', @@ -248,8 +248,8 @@ from the saved variables. `result.usage` and `result.steps` include the earlier attempts. ```ts -import { FlowExecutor, type AgentFlow } from 'lousho/flows'; -import { memoryStore } from 'lousho'; +import { FlowExecutor, type AgentFlow } from '@linuxdevil/lousho/flows'; +import { memoryStore } from '@linuxdevil/lousho'; declare const flow: AgentFlow; const checkpointStore = memoryStore().checkpoints; @@ -318,8 +318,8 @@ are cancelled. Steps they completed are kept, and the rest run again on resume. To continue, pass the decision to `resume()`: ```ts -import { FlowExecutor, type AgentFlow } from 'lousho/flows'; -import { memoryStore } from 'lousho'; +import { FlowExecutor, type AgentFlow } from '@linuxdevil/lousho/flows'; +import { memoryStore } from '@linuxdevil/lousho'; declare const flow: AgentFlow; const checkpointStore = memoryStore().checkpoints; @@ -366,7 +366,7 @@ asked, and the context's `approve` callback decides, exactly as for a node's `id`, or its place in the flow). ```ts -import type { EditorStep } from 'lousho/flows'; +import type { EditorStep } from '@linuxdevil/lousho/flows'; const steps: EditorStep = { type: 'sequence', @@ -387,7 +387,7 @@ const steps: EditorStep = { the decision: ```ts no-run -import { FlowExecutor, type AgentFlow } from 'lousho/flows'; +import { FlowExecutor, type AgentFlow } from '@linuxdevil/lousho/flows'; declare const flow: AgentFlow; @@ -411,8 +411,8 @@ An `agent` step calls a `createAgent()` agent registered in the context's guardrails and approvals — and the step stores its reply text: ```ts -import { createAgent } from 'lousho'; -import { FlowExecutor, type AgentFlow } from 'lousho/flows'; +import { createAgent } from '@linuxdevil/lousho'; +import { FlowExecutor, type AgentFlow } from '@linuxdevil/lousho/flows'; const writer = createAgent({ name: 'writer', model: 'openai/gpt-4o-mini', instructions: 'Draft a reply.' }); @@ -457,9 +457,9 @@ typed value rather than text: ```ts import { z } from 'zod'; -import { createAgent } from 'lousho'; -import { FlowExecutor, type AgentFlow } from 'lousho/flows'; -import { ToolRegistry } from 'lousho/tools'; +import { createAgent } from '@linuxdevil/lousho'; +import { FlowExecutor, type AgentFlow } from '@linuxdevil/lousho/flows'; +import { ToolRegistry } from '@linuxdevil/lousho/tools'; const researcher = createAgent({ name: 'researcher', model: 'openai/gpt-4o-mini', instructions: 'Answer with three facts.' }); diff --git a/docs/guardrails.md b/docs/guardrails.md index 9397f66bb..afc75c6e1 100644 --- a/docs/guardrails.md +++ b/docs/guardrails.md @@ -47,7 +47,7 @@ OpenRouter use `openrouter//` (e.g. `openrouter/openai/gpt-4o-mini`). ```ts -import { createAgent, moderationGuardrail, piiGuardrail } from 'lousho'; +import { createAgent, moderationGuardrail, piiGuardrail } from '@linuxdevil/lousho'; const agent = createAgent({ provider, @@ -87,7 +87,7 @@ import { maxLengthGuardrail, regexGuardrail, type IoGuardrail, -} from 'lousho'; +} from '@linuxdevil/lousho'; const noProdWrites: IoGuardrail = { name: 'no-prod-writes', @@ -133,7 +133,7 @@ try { The last four are a starter set for checks most production agents need: ```ts -import { createAgent, piiGuardrail, promptInjectionGuardrail, secretsGuardrail } from 'lousho'; +import { createAgent, piiGuardrail, promptInjectionGuardrail, secretsGuardrail } from '@linuxdevil/lousho'; const agent = createAgent({ provider, @@ -181,7 +181,7 @@ proposed patch and rolls the results up into one verdict. The agent's patch before a pull request is opened: ```ts -import { runPatchChecks, secretScanCheck, createDiffSizeCheck, createCommandCheck } from 'lousho'; +import { runPatchChecks, secretScanCheck, createDiffSizeCheck, createCommandCheck } from '@linuxdevil/lousho'; const verdict = await runPatchChecks( { diff: patch }, @@ -221,8 +221,8 @@ function. The executor then calls `sandboxExecute` with the run's #### Advanced: the executor API ```ts -import { AgentExecutor } from 'lousho/executor'; -import { SubprocessSandbox } from 'lousho'; +import { AgentExecutor } from '@linuxdevil/lousho/executor'; +import { SubprocessSandbox } from '@linuxdevil/lousho'; // Route a flagged tool (requiresSandbox + sandboxExecute) through a real, // Docker-backed sandbox instead of the in-process NoopSandbox default diff --git a/docs/handoffs.md b/docs/handoffs.md index 2300c921a..3cf7b724b 100644 --- a/docs/handoffs.md +++ b/docs/handoffs.md @@ -19,7 +19,7 @@ description to decide when to hand off), then pass them to the triage agent as use `openrouter//` (e.g. `openrouter/openai/gpt-4o-mini`). ```ts -import { createAgent, defineTool } from 'lousho'; +import { createAgent, defineTool } from '@linuxdevil/lousho'; import { z } from 'zod'; const lookupCharges = defineTool({ @@ -70,7 +70,7 @@ name with one of the agent's tools. Wrap a target in `handoff()` to configure it: ```ts -import { createAgent, handoff, handoffFilters } from 'lousho'; +import { createAgent, handoff, handoffFilters } from '@linuxdevil/lousho'; import { z } from 'zod'; const refunds = createAgent({ name: 'refunds', description: 'Issues refunds', model: 'openai/gpt-4o-mini' }); @@ -122,7 +122,7 @@ the middle of a conversation. Two filters are built in: - `handoffFilters.lastUserMessage`: keeps only the last user message. ```ts -import { handoffFilters, type HandoffInputData } from 'lousho'; +import { handoffFilters, type HandoffInputData } from '@linuxdevil/lousho'; // The last four messages, without tool traffic. const recent = (data: HandoffInputData) => handoffFilters.removeToolCalls(data).slice(-4); @@ -169,7 +169,7 @@ In a session the active agent is the one the transcript last handed off to `session.send()` / `session.stream()` runs that agent: ```ts no-run -import { createAgent } from 'lousho'; +import { createAgent } from '@linuxdevil/lousho'; const billing = createAgent({ name: 'billing', description: 'Handles charges and refunds', model: 'openai/gpt-4o-mini' }); const triage = createAgent({ name: 'triage', model: 'openai/gpt-4o-mini', handoffs: [billing] }); @@ -187,7 +187,7 @@ its targets, give the target an array and add the lead to it afterwards (the array is read at every run): ```ts -import { createAgent, handoff, type Handoff } from 'lousho'; +import { createAgent, handoff, type Handoff } from '@linuxdevil/lousho'; const billingHandoffs: Handoff[] = []; const billing = createAgent({ @@ -237,7 +237,7 @@ A handoff is reported between the handoff call's `tool.start` / `tool.done` and the target's first `step.start`: ```ts no-run -import { createAgent } from 'lousho'; +import { createAgent } from '@linuxdevil/lousho'; const billing = createAgent({ name: 'billing', description: 'Handles charges and refunds', model: 'openai/gpt-4o-mini' }); const triage = createAgent({ name: 'triage', model: 'openai/gpt-4o-mini', handoffs: [billing] }); diff --git a/docs/hooks.md b/docs/hooks.md index 814eb1196..039b070b2 100644 --- a/docs/hooks.md +++ b/docs/hooks.md @@ -9,8 +9,8 @@ Hooks are code you write, not policy and not a human decision: for rules that al Pass hooks to `createAgent({ hooks })`. This one logs every tool call and what it returned: ```ts -import { createAgent, defineTool, type AgentHook } from 'lousho'; -import { mockModel } from 'lousho/testing'; +import { createAgent, defineTool, type AgentHook } from '@linuxdevil/lousho'; +import { mockModel } from '@linuxdevil/lousho/testing'; import { z } from 'zod'; const auditHook: AgentHook = { @@ -81,8 +81,8 @@ that throws still rejects the run, as before. Sub-agents inherit the lead agent's hooks and their outcomes. ```ts -import { createAgent, type AgentHook } from 'lousho'; -import { mockModel } from 'lousho/testing'; +import { createAgent, type AgentHook } from '@linuxdevil/lousho'; +import { mockModel } from '@linuxdevil/lousho/testing'; const provider = mockModel(['Done.']); @@ -124,8 +124,8 @@ getting anywhere. It's off unless you add it: `LOUSHO_RUN_NO_PROGRESS`, which `agent.recover()` won't retry. ```ts -import { createAgent, createProgressGuard, defineTool } from 'lousho'; -import { mockModel } from 'lousho/testing'; +import { createAgent, createProgressGuard, defineTool } from '@linuxdevil/lousho'; +import { mockModel } from '@linuxdevil/lousho/testing'; import { z } from 'zod'; const lookup = defineTool({ @@ -163,8 +163,8 @@ sees it as `outputCheckFailed` on the result and can fix the output. The check is your code, so make it independent of what the model wrote. ```ts -import { createAgent, createOutputCheck, defineTool } from 'lousho'; -import { mockModel } from 'lousho/testing'; +import { createAgent, createOutputCheck, defineTool } from '@linuxdevil/lousho'; +import { mockModel } from '@linuxdevil/lousho/testing'; import { z } from 'zod'; const files = new Map(); @@ -194,8 +194,8 @@ await agent.send('Write the report'); ### Deny a tool by name ```ts -import { createAgent, defineTool, type AgentHook } from 'lousho'; -import { mockModel } from 'lousho/testing'; +import { createAgent, defineTool, type AgentHook } from '@linuxdevil/lousho'; +import { mockModel } from '@linuxdevil/lousho/testing'; import { z } from 'zod'; const shell = defineTool({ @@ -227,8 +227,8 @@ console.log(result.text); Return `{ input }` to run the tool with different arguments. They are validated against the tool's schema again. ```ts -import { createAgent, defineTool, type AgentHook } from 'lousho'; -import { mockModel } from 'lousho/testing'; +import { createAgent, defineTool, type AgentHook } from '@linuxdevil/lousho'; +import { mockModel } from '@linuxdevil/lousho/testing'; import { z } from 'zod'; const search = defineTool({ @@ -261,8 +261,8 @@ await agent.send('Search for hooks'); `postToolCall` receives the settled result and may return `{ result }` to replace what the model sees. The tool itself ran unchanged. ```ts -import { createAgent, defineTool, type AgentHook } from 'lousho'; -import { mockModel } from 'lousho/testing'; +import { createAgent, defineTool, type AgentHook } from '@linuxdevil/lousho'; +import { mockModel } from '@linuxdevil/lousho/testing'; import { z } from 'zod'; const readLog = defineTool({ @@ -296,8 +296,8 @@ console.log(toolMessage?.role); // 'tool': its content is the redacted text `preGenerate` sees the live `ctx.messages` and `ctx.request`. Push a message to add context, or change a request field. ```ts -import { createAgent, type AgentHook } from 'lousho'; -import { mockModel } from 'lousho/testing'; +import { createAgent, type AgentHook } from '@linuxdevil/lousho'; +import { mockModel } from '@linuxdevil/lousho/testing'; const addDate: AgentHook = { name: 'add-date', @@ -335,7 +335,7 @@ refused with a tool error instead of running. See [Approvals](./approvals.md) fo `createAgent({ hooks })` takes a plain array. Code that uses the executor API ([The executor API](./executor-api.md)) passes a `HookRegistry`: an ordered collection with `register`, `registerMany`, `get`, `has`, `list`, `unregister`, `clear` and `size()` — `size` is a method (`hooks.size()`), not a `Map`-style property. Registering a second hook with the same `name` replaces the first and logs a warning. ```ts -import { HookRegistry, type AgentHook } from 'lousho/hooks'; +import { HookRegistry, type AgentHook } from '@linuxdevil/lousho/hooks'; const redactPii: AgentHook = { name: 'redact-pii', @@ -349,4 +349,4 @@ hooks.register(redactPii); console.log(hooks.list().map((hook) => hook.name)); // [ 'redact-pii' ] ``` -`HookRegistry`, `AgentHook` and the context types are available from the package root and from the `lousho/hooks` subpath. Hooks authored in the Agent Forge canvas editor are compiled into a `HookRegistry` and run sandboxed rather than in the host process: see [Agent Forge](./agent-forge.md#hooks). +`HookRegistry`, `AgentHook` and the context types are available from the package root and from the `@linuxdevil/lousho/hooks` subpath. Hooks authored in the Agent Forge canvas editor are compiled into a `HookRegistry` and run sandboxed rather than in the host process: see [Agent Forge](./agent-forge.md#hooks). diff --git a/docs/hosted-tools.md b/docs/hosted-tools.md index 94ab004ec..8bd0d70af 100644 --- a/docs/hosted-tools.md +++ b/docs/hosted-tools.md @@ -6,7 +6,7 @@ web search, code interpreter and file search are the common ones. Put them in each call in its events, keeps it on the transcript and counts it in usage. ```ts -import { createAgent, webSearch, defineTool } from 'lousho'; +import { createAgent, webSearch, defineTool } from '@linuxdevil/lousho'; import { z } from 'zod'; const saveNote = defineTool({ @@ -109,7 +109,7 @@ providers and with any AI SDK model wrapped by [`fromAiSdk()`](./providers.md#any-ai-sdk-model-fromaisdk): ```ts no-verify -import { createAgent, fromAiSdk, hostedTool } from 'lousho'; +import { createAgent, fromAiSdk, hostedTool } from '@linuxdevil/lousho'; import { openai } from '@ai-sdk/openai'; const agent = createAgent({ @@ -126,7 +126,7 @@ A hosted call is reported like a tool call, with `executedBy: 'provider'` on `tool.start`, `tool.done` and `tool.error`: ```ts -import { createAgent, webSearch } from 'lousho'; +import { createAgent, webSearch } from '@linuxdevil/lousho'; const agent = createAgent({ model: 'openai/gpt-4o-mini', tools: [webSearch()] }); @@ -180,7 +180,7 @@ without a human, leave the tool out of `tools`, or choose tools per run with a function: ```ts -import { createAgent, webSearch } from 'lousho'; +import { createAgent, webSearch } from '@linuxdevil/lousho'; const agent = createAgent({ model: 'openai/gpt-4o-mini', @@ -217,8 +217,8 @@ hosted tools; a resuming agent with a different set reports request, so an agent with hosted tools runs offline: ```ts -import { createAgent, webSearch } from 'lousho'; -import { mockModel } from 'lousho/testing'; +import { createAgent, webSearch } from '@linuxdevil/lousho'; +import { mockModel } from '@linuxdevil/lousho/testing'; const model = mockModel([ { diff --git a/docs/improve.md b/docs/improve.md index 895cc1ac4..47a19d24e 100644 --- a/docs/improve.md +++ b/docs/improve.md @@ -32,9 +32,9 @@ It's experimental: its results so far come from a small benchmark. For how this fits with recovering failed runs while the agent serves traffic, see [Self-healing and self-improvement](./self-healing.md). ```ts -import { createAgent, defineTool } from 'lousho'; -import { formatImproveResult, improveAgent, renderTranscript, type Harness } from 'lousho/improve'; -import { mockModel } from 'lousho/testing'; +import { createAgent, defineTool } from '@linuxdevil/lousho'; +import { formatImproveResult, improveAgent, renderTranscript, type Harness } from '@linuxdevil/lousho/improve'; +import { mockModel } from '@linuxdevil/lousho/testing'; import { z } from 'zod'; const search = defineTool({ @@ -218,7 +218,7 @@ files. your own function, keep its `cached` field so the run is counted as cached. ```ts -import { cachedProbe, improveAgent, type Harness, type ImproveOptions } from 'lousho/improve'; +import { cachedProbe, improveAgent, type Harness, type ImproveOptions } from '@linuxdevil/lousho/improve'; declare const harness: Harness; declare const probe: ImproveOptions['probe']; @@ -301,7 +301,7 @@ gate changes: it judges the harness, not the reasons. A `CaseCheck` returns `true`, `false`, `null` when it can't score the answer, or `{ passed, failure }`. Return `failure` on a failed answer to name the check it missed, as a custom `probe` does: each editor reads it in that run's record, after `Result: failed`, on one line with forged untrusted markers removed. A check must be synchronous; any other return value throws a `ConfigurationError`. Without it, a failed run whose answer looks right gives the editors nothing to fix. Name the real check; a wrong name is worse than none. A `failure` on a passed answer is dropped. ```ts -import type { CaseCheck } from 'lousho/improve'; +import type { CaseCheck } from '@linuxdevil/lousho/improve'; declare function failedTests(input: string, answer: string): string[]; @@ -316,7 +316,7 @@ const check: CaseCheck = (input, answer) => { With `retryWithDiagnosis`, [`agent.recover()`](./self-healing.md#recover-a-run-now) forks a run that kept failing into a `.heal` session told why it failed. When that fork passes, you hold a pair on the same input: the failed run and a retry that passed. `casesFromRecoveries(db, filter)` finds those pairs in your local database and returns the failed runs as cases, each with a `recovery`: the retry's transcript, rendered with `renderTranscript`, the diagnosis it was given, and its final answer. A fork counts only when it carries the diagnosis and finished with no recorded error, no answer cut short and no low score. That isn't your check, so score each `recovery.answer` with your `CaseCheck` and keep only the ones that pass, then pass them on as `recoveries`: ```ts -import { casesFromRecoveries, improveAgent, type CaseCheck, type Harness, type ImproveOptions, type SqlDatabase } from 'lousho/improve'; +import { casesFromRecoveries, improveAgent, type CaseCheck, type Harness, type ImproveOptions, type SqlDatabase } from '@linuxdevil/lousho/improve'; declare const db: SqlDatabase; declare const harness: Harness; @@ -351,7 +351,7 @@ A library keeps lessons from earlier attempts, so later editors can read them. E `libraryFiles(dir, domain)` returns up to 20 active lessons as files under `context/library/`, highest-voted first, then newest. Each file is labelled as data with the lesson between untrusted markers, and you pass them to `improveAgent` as `library`. Entries are checked again when read, so an entry file edited by hand that no longer passes is skipped. `voteEntry(dir, id, 'helped' | 'hurt')` counts an outcome, and retires a lesson for good once its hurt votes outnumber its helped votes by two. Votes are appended to `votes.jsonl` next to the entries, so votes cast at once aren't lost. `improveAgent` accepts only files shaped like `libraryFiles` output, named `context/library/.md`. ```ts -import { improveAgent, libraryFiles, libraryHome, type Harness, type ImproveOptions } from 'lousho/improve'; +import { improveAgent, libraryFiles, libraryHome, type Harness, type ImproveOptions } from '@linuxdevil/lousho/improve'; declare const harness: Harness; declare const probe: ImproveOptions['probe']; @@ -367,7 +367,7 @@ const result = await improveAgent({ harness, cases: ['stuck'], probe, editor: { Once a cycle is decided, `reflect` turns it into at most one lesson. A cycle is decided when its edit was kept, rejected, or applied and then rolled back. A rejected or rolled-back edit teaches as much as a kept one. The reflection session reads four files and nothing else: the review card, the diff, the case verdicts and the outcome. It never sees a raw transcript, and the text that models wrote reaches it between untrusted markers. It replies with an `` block (cue, lesson, evidence, confidence) or `NO_ENTRY`, and the lesson goes through `addEntry` with the cycle's failed checks, so a refused lesson is reported and never stored. `reflect` never throws. A failure comes back as `error` for you to record, and `tokens` is what it spent, to count toward the cycle's budget, which `maxTokens` caps. ```ts -import { libraryHome, reflect, voteOnOutcome, type ImproveResult } from 'lousho/improve'; +import { libraryHome, reflect, voteOnOutcome, type ImproveResult } from '@linuxdevil/lousho/improve'; declare const result: ImproveResult; @@ -391,7 +391,7 @@ The session never sees a held-out case's id: each held-out case appears as `held `settleCanary` reads the trial; call it yourself on a timer when you don't use the watcher. After `sessions` candidate sessions it promotes when the share of flagged sessions is 0 or at least 0.05 below the base's rate over its last 20 runs, and no failure class appeared that the base didn't have. It retires early once a promotion is ruled out, and when the directory changed, the loop is paused, the opt-in is removed or `monitor.giveUpDays` pass. A promote goes through `applyProposal`, which checks the proposal again. A canary started in auto mode is queued for a person instead when `improve.json` has since switched to manual. In manual mode, `startCanary` refuses without `approvedBy`, and a canary without an approver serves no one. ```ts -import { loadImproveSettings, settleCanary, startCanary, type Harness, type ImproveResult, type SqlDatabase } from 'lousho/improve'; +import { loadImproveSettings, settleCanary, startCanary, type Harness, type ImproveResult, type SqlDatabase } from '@linuxdevil/lousho/improve'; declare const db: SqlDatabase; declare const result: ImproveResult; @@ -474,7 +474,7 @@ See [The monitor](#the-monitor). under a harness; `lousho improve` builds it for you. ```ts -import { healOnce, loadImproveSettings, type CaseCheck, type HealOptions, type SqlDatabase } from 'lousho/improve'; +import { healOnce, loadImproveSettings, type CaseCheck, type HealOptions, type SqlDatabase } from '@linuxdevil/lousho/improve'; declare const db: SqlDatabase; declare const check: CaseCheck; @@ -596,7 +596,7 @@ tick as a [schedule](./schedules.md) for an agent that already uses `schedules/`: ```ts -import { improveSchedule, loadImproveSettings, watchAgent, type CaseCheck, type HealOptions, type SqlDatabase } from 'lousho/improve'; +import { improveSchedule, loadImproveSettings, watchAgent, type CaseCheck, type HealOptions, type SqlDatabase } from '@linuxdevil/lousho/improve'; declare const db: SqlDatabase; declare const check: CaseCheck; diff --git a/docs/installation.md b/docs/installation.md index 086b87533..4f0bc0f4d 100644 --- a/docs/installation.md +++ b/docs/installation.md @@ -9,7 +9,7 @@ ## Install the package ```bash -npm install lousho ai zod +npm install @linuxdevil/lousho ai zod # or pnpm add lousho ai zod # or @@ -42,7 +42,7 @@ that pairs with your `ai` major. Install the pair from one row: For example, on the current `ai` major: ```bash -npm install lousho ai@^7.0.0 zod @ai-sdk/openai@^4.0.0 +npm install @linuxdevil/lousho ai@^7.0.0 zod @ai-sdk/openai@^4.0.0 ``` **Ollama on `ai` 6/7 needs zod 4.** `ollama-ai-provider-v2` (the Ollama package @@ -74,7 +74,7 @@ npm install @ai-sdk/openai@^0.0.42 ``` ```ts no-run -import { MissingPeerDependencyError, resolveProvider } from 'lousho'; +import { MissingPeerDependencyError, resolveProvider } from '@linuxdevil/lousho'; const provider = resolveProvider('openai/gpt-4o-mini'); // needs OPENAI_API_KEY; does not load the peer try { @@ -96,9 +96,9 @@ the `lousho init` questions or code mode installs none of them: | --------------------------- | --------- | --------------------------------------------------------------------------------------------- | ------------------------------------------------ | | `dockerode` | `^5.0.1` | Docker sandboxing: `SubprocessSandbox` (and `lousho doctor`'s Docker ping) | `npm install dockerode@^5.0.1` | | `@modelcontextprotocol/sdk` | `^1.31.0` | MCP: `createAgent({ mcpServers })` / `connectMcp()`, `serveMcp()` and `lousho mcp` | `npm install @modelcontextprotocol/sdk@^1.31.0` | -| `@opentelemetry/api` | `^1.9.1` | OpenTelemetry tracing: `createOtelTraceExporter()` from `lousho/otel` | `npm install @opentelemetry/api@^1.9.1` | -| `react` | `^18`, `^19` | The React bindings (`lousho/react`) | `npm install react` | -| `vue` | `^3` | The Vue bindings (`lousho/vue`) | `npm install vue@^3` | +| `@opentelemetry/api` | `^1.9.1` | OpenTelemetry tracing: `createOtelTraceExporter()` from `@linuxdevil/lousho/otel` | `npm install @opentelemetry/api@^1.9.1` | +| `react` | `^18`, `^19` | The React bindings (`@linuxdevil/lousho/react`) | `npm install react` | +| `vue` | `^3` | The Vue bindings (`@linuxdevil/lousho/vue`) | `npm install vue@^3` | | `tsup` | `^8.0.0` | `lousho build`, which bundles the agent for each target | `npm install --save-dev tsup@^8.0.0` | | `prompts` | `^2.4.2` | The interactive questions of `lousho init` (`lousho init --yes` and flags need nothing) | `npm install prompts@^2.4.2` | | `quickjs-emscripten` | `^0.32.0` | [Code mode](./code-mode.md): `createAgent({ codeMode })` runs `run_code` scripts in its QuickJS isolate | `npm install quickjs-emscripten@^0.32.0` | @@ -152,24 +152,24 @@ The entries `exports` declares, and what each one holds: | Import specifier | What it holds | | ---------------- | ------------- | | `lousho` | The root: `createAgent()`, `defineTool()`, `defineChannel()`, agents' public types, events, stores (`memoryStore()`, `fileStore()`), hooks, guardrails, sessions, skills, memory, sub-agents, evals, spec files, deployment types | -| `lousho/executor` | The executor API (advanced - prefer `createAgent()`): `AgentExecutor`, `AgentBuilder`, `resumeAfterApproval()`, `streamResumeAfterApproval()`, `resumeRequest()`, `ToolRegistry`, `globalToolRegistry` | -| `lousho/tools` | The tools surface: `defineTool()`, `ToolRegistry`, the built-in tools, MCP/OpenAPI helpers | -| `lousho/flows` | `FlowBuilder`, `FlowExecutor`, `validateFlow()` and every flow type (`AgentFlow`, `EditorStep`, `FlowExecutionEvent`, ...) | -| `lousho/integrations` | The credentialed third-party tools: `createJiraTools()`, `createGitHubTools()`, `createSlackTool()` / `slackTool`, `postSlackAlert()`, `createEmailTool()` and their types | -| `lousho/utils` | `EncryptionUtils`, `sha256()`, `generatePassword()`, `StorageService`, `StorageServiceApprovalStore`, `LocalStorageCheckpointStore` and the validators (`validateWithSchema`, `isValidEmail`, ...) | -| `lousho/mcp` | MCP client and server helpers: `connectMcp()`, `loadMcpTools()`, `serveMcp()` | -| `lousho/types` | The public types without the runtime | -| `lousho/testing` | `mockModel()`, `recordReplay()` and the offline-test helpers | -| `lousho/otel` | `createOtelTraceExporter()` (needs the optional `@opentelemetry/api` peer) | -| `lousho/hooks` | The hooks surface: `HookRegistry`, hook types | -| `lousho/sqlite` | `SqliteStore` (sessions, checkpoints, approvals and tokens in one SQLite file; needs Node's `node:sqlite`) | -| `lousho/auth` | Route auth: `routeAuth()`, `jwt()`, `oidc()`, `basic()`, `apiToken()`, `anonymous()` | -| `lousho/kv` | `KVStore`, `KVCheckpointStore` for a hand-written Cloudflare Worker | -| `lousho/traces` | `fileTraceExporter()`, `listTraces()`, `readTrace()` (what `lousho traces` reads) | -| `lousho/triggers` | `TriggerAdapter`, `TriggerRegistry`, `verifySlackSignature`, `parseCronExpression` | -| `lousho/react` | `useLoushoAgent()` and the React helpers (needs `react`) | -| `lousho/vue` | `useLoushoAgent()` as a Vue composable (needs `vue`) | -| `lousho/svelte` | `loushoAgent()` as a Svelte store | +| `@linuxdevil/lousho/executor` | The executor API (advanced - prefer `createAgent()`): `AgentExecutor`, `AgentBuilder`, `resumeAfterApproval()`, `streamResumeAfterApproval()`, `resumeRequest()`, `ToolRegistry`, `globalToolRegistry` | +| `@linuxdevil/lousho/tools` | The tools surface: `defineTool()`, `ToolRegistry`, the built-in tools, MCP/OpenAPI helpers | +| `@linuxdevil/lousho/flows` | `FlowBuilder`, `FlowExecutor`, `validateFlow()` and every flow type (`AgentFlow`, `EditorStep`, `FlowExecutionEvent`, ...) | +| `@linuxdevil/lousho/integrations` | The credentialed third-party tools: `createJiraTools()`, `createGitHubTools()`, `createSlackTool()` / `slackTool`, `postSlackAlert()`, `createEmailTool()` and their types | +| `@linuxdevil/lousho/utils` | `EncryptionUtils`, `sha256()`, `generatePassword()`, `StorageService`, `StorageServiceApprovalStore`, `LocalStorageCheckpointStore` and the validators (`validateWithSchema`, `isValidEmail`, ...) | +| `@linuxdevil/lousho/mcp` | MCP client and server helpers: `connectMcp()`, `loadMcpTools()`, `serveMcp()` | +| `@linuxdevil/lousho/types` | The public types without the runtime | +| `@linuxdevil/lousho/testing` | `mockModel()`, `recordReplay()` and the offline-test helpers | +| `@linuxdevil/lousho/otel` | `createOtelTraceExporter()` (needs the optional `@opentelemetry/api` peer) | +| `@linuxdevil/lousho/hooks` | The hooks surface: `HookRegistry`, hook types | +| `@linuxdevil/lousho/sqlite` | `SqliteStore` (sessions, checkpoints, approvals and tokens in one SQLite file; needs Node's `node:sqlite`) | +| `@linuxdevil/lousho/auth` | Route auth: `routeAuth()`, `jwt()`, `oidc()`, `basic()`, `apiToken()`, `anonymous()` | +| `@linuxdevil/lousho/kv` | `KVStore`, `KVCheckpointStore` for a hand-written Cloudflare Worker | +| `@linuxdevil/lousho/traces` | `fileTraceExporter()`, `listTraces()`, `readTrace()` (what `lousho traces` reads) | +| `@linuxdevil/lousho/triggers` | `TriggerAdapter`, `TriggerRegistry`, `verifySlackSignature`, `parseCronExpression` | +| `@linuxdevil/lousho/react` | `useLoushoAgent()` and the React helpers (needs `react`) | +| `@linuxdevil/lousho/vue` | `useLoushoAgent()` as a Vue composable (needs `vue`) | +| `@linuxdevil/lousho/svelte` | `loushoAgent()` as a Svelte store | ### TypeScript settings @@ -239,8 +239,8 @@ one-line `CLAUDE.md` importing it) telling coding agents the project's files, scripts and conventions. It then installs the dependencies and runs `git init`. ```bash -npm create lousho my-agent # or: npx lousho init my-agent -npx lousho init my-agent --yes --provider anthropic --template tools --no-install +npm create lousho my-agent # or: npx @linuxdevil/lousho init my-agent +npx @linuxdevil/lousho init my-agent --yes --provider anthropic --template tools --no-install ``` | Flag | Meaning | @@ -265,7 +265,7 @@ an optional peer (see the table above): `npm install --save-dev tsup`. ## Troubleshooting: lousho doctor -Run `npx lousho doctor` right after installing. It prints one line per check +Run `npx @linuxdevil/lousho doctor` right after installing. It prints one line per check with a status (`ok`, `warn`, `FAIL`), what it found, and, for anything that is not ok, the command to run or the setting to change: diff --git a/docs/mcp.md b/docs/mcp.md index 583746af5..fcf8ea785 100644 --- a/docs/mcp.md +++ b/docs/mcp.md @@ -2,7 +2,7 @@ MCP works in two directions. Your agent can call the tools of MCP servers, or your agent can be an MCP server that Claude Code, Cursor and other MCP clients -call. Both live in the `lousho/mcp` subpath, and +call. Both live in the `@linuxdevil/lousho/mcp` subpath, and `@modelcontextprotocol/sdk` is an optional peer: install it to use either direction. @@ -11,7 +11,7 @@ npm install @modelcontextprotocol/sdk ``` `connectMcp`, `loadMcpTools` and `serveMcp` are exported from -`lousho/mcp`; the package root and `lousho/tools` +`@linuxdevil/lousho/mcp`; the package root and `@linuxdevil/lousho/tools` export them too. `createAgent({ mcpServers })` needs no import from the subpath. ## Use MCP servers in an agent @@ -26,7 +26,7 @@ or `ready()`, reconnects. Without `mcpServers`, `ready()` and `close()` do nothi `openrouter//` (e.g. `openrouter/openai/gpt-4o-mini`). ```ts no-run -import { createAgent } from 'lousho'; +import { createAgent } from '@linuxdevil/lousho'; const agent = createAgent({ model: 'openai/gpt-4o-mini', @@ -122,8 +122,8 @@ own tools and MCP tools can sit in one list. (Spreading into one record, `tools: { ...mcp.tools, my_tool: myTool }`, works too.) ```ts no-run -import { createAgent } from 'lousho'; -import { connectMcp } from 'lousho/mcp'; +import { createAgent } from '@linuxdevil/lousho'; +import { connectMcp } from '@linuxdevil/lousho/mcp'; const mcp = await connectMcp( { files: { command: 'npx', args: ['-y', '@modelcontextprotocol/server-filesystem', '.'] } }, @@ -200,7 +200,7 @@ in `loadMcpTools(client, name, { approval })`: `allow`, `deny` or `ask`. ```ts no-run -import { createAgent } from 'lousho'; +import { createAgent } from '@linuxdevil/lousho'; const agent = createAgent({ model: 'openai/gpt-4o-mini', @@ -224,15 +224,15 @@ its tools with `loadMcpTools()`, then pass the result to `createAgent()` (or register it on a `ToolRegistry`): ```ts -import { createAgent } from 'lousho'; -import { loadMcpTools } from 'lousho/mcp'; +import { createAgent } from '@linuxdevil/lousho'; +import { loadMcpTools } from '@linuxdevil/lousho/mcp'; const tools = await loadMcpTools(mcpClient, 'my-server'); const agent = createAgent({ instructions: '...', provider, tools }); ``` `loadMcpTools` is also available from the package root and from -`lousho/tools`. +`@linuxdevil/lousho/tools`. ## Serve an agent over MCP @@ -241,8 +241,8 @@ optionally, some of its tools) as an MCP server, so Claude Code, Cursor and other MCP clients can call it. ```ts -import { createAgent, defineTool } from 'lousho'; -import { serveMcp } from 'lousho/mcp'; +import { createAgent, defineTool } from '@linuxdevil/lousho'; +import { serveMcp } from '@linuxdevil/lousho/mcp'; import { z } from 'zod'; const searchDocs = defineTool({ @@ -307,7 +307,7 @@ needs approval is never advertised read-only. The built-in `read_file`, `list_di `glob`, `grep`, `todo_read`, `current_date` and `day_name` tools are read-only. ```ts -import { defineTool } from 'lousho'; +import { defineTool } from '@linuxdevil/lousho'; import { z } from 'zod'; const lookupOrder = defineTool({ @@ -324,8 +324,8 @@ const lookupOrder = defineTool({ `lousho mcp` serves an agent spec file (stdio by default): ```sh -npx lousho mcp agent.yaml -npx lousho mcp agent.yaml --http --port 3920 --host 127.0.0.1 +npx @linuxdevil/lousho mcp agent.yaml +npx @linuxdevil/lousho mcp agent.yaml --http --port 3920 --host 127.0.0.1 ``` To use it from an MCP client, add it to the client's MCP config (for example diff --git a/docs/memory.md b/docs/memory.md index 70cf97379..89276b3c5 100644 --- a/docs/memory.md +++ b/docs/memory.md @@ -36,7 +36,7 @@ The `vendor/` model prefix chooses the provider; with OpenRouter use `openrouter//` (e.g. `openrouter/openai/gpt-4o-mini`). ```ts no-run -import { createAgent, defineMemory, fileMemory } from 'lousho'; +import { createAgent, defineMemory, fileMemory } from '@linuxdevil/lousho'; const preferences = defineMemory({ name: 'preferences', @@ -84,8 +84,8 @@ A scope function sees the run's `sessionId` and the `metadata` passed to sessions: ```ts -import { createAgent, defineMemory, inMemoryMemory } from 'lousho'; -import { mockModel } from 'lousho/testing'; +import { createAgent, defineMemory, inMemoryMemory } from '@linuxdevil/lousho'; +import { mockModel } from '@linuxdevil/lousho/testing'; const userFacts = defineMemory({ name: 'user_facts', @@ -128,7 +128,7 @@ every start: `add()` dedupes on text, so nothing is copied twice. Each copy keeps `text` and `metadata` and gets a new `id` and `createdAt`. ```ts -import { defineMemory, fileMemory, migrateMemoryKeys } from 'lousho'; +import { defineMemory, fileMemory, migrateMemoryKeys } from '@linuxdevil/lousho'; const provider = fileMemory({ dir: './.lousho/memory' }); const notes = defineMemory({ name: 'notes', scope: 'global', provider }); @@ -166,7 +166,7 @@ the count. ```ts import { z } from 'zod'; -import { defineMemory, inMemoryMemory } from 'lousho'; +import { defineMemory, inMemoryMemory } from '@linuxdevil/lousho'; const relationship = defineMemory({ name: 'relationship', @@ -197,7 +197,7 @@ adding a second version. The item keeps its id, becomes the newest, and ```ts import { z } from 'zod'; -import { defineMemory, inMemoryMemory } from 'lousho'; +import { defineMemory, inMemoryMemory } from '@linuxdevil/lousho'; const settings = defineMemory({ name: 'settings', @@ -283,8 +283,8 @@ by meaning instead of by keyword, use `sqliteVectorMemory()`: see [Semantic recall](#semantic-recall). ```ts no-run -import { createAgent, defineMemory } from 'lousho'; -import { SqliteStore, sqliteMemory } from 'lousho/sqlite'; +import { createAgent, defineMemory } from '@linuxdevil/lousho'; +import { SqliteStore, sqliteMemory } from '@linuxdevil/lousho/sqlite'; const store = new SqliteStore('./.lousho/agent.db'); const notes = defineMemory({ name: 'notes', scope: 'global', provider: sqliteMemory(store) }); @@ -294,7 +294,7 @@ const agent = createAgent({ model: 'openai/gpt-4o-mini', store, memory: [notes] For a hosted store or your own ranking, implement `MemoryProvider`: ```ts -import type { MemoryItem, MemoryProvider } from 'lousho'; +import type { MemoryItem, MemoryProvider } from '@linuxdevil/lousho'; const items = new Map(); const myProvider: MemoryProvider = { @@ -323,8 +323,8 @@ With [`mockModel`](./testing.md), assert on the system prompt of the first request and drive the tools with scripted tool calls: ```ts -import { createAgent, defineMemory, inMemoryMemory, memoryKey } from 'lousho'; -import { mockModel } from 'lousho/testing'; +import { createAgent, defineMemory, inMemoryMemory, memoryKey } from '@linuxdevil/lousho'; +import { mockModel } from '@linuxdevil/lousho/testing'; const store = inMemoryMemory(); const notes = defineMemory({ name: 'notes', scope: 'global', provider: store }); @@ -366,7 +366,7 @@ package that pairs with your `ai` major, as for language models). It adds no dependency: `ai` is already a peer, and the provider package is yours. ```ts no-run -import { createAgent, defineMemory, aiSdkEmbedder, inMemoryVectorMemory } from 'lousho'; +import { createAgent, defineMemory, aiSdkEmbedder, inMemoryVectorMemory } from '@linuxdevil/lousho'; import { createOpenAI } from '@ai-sdk/openai'; const openai = createOpenAI({ apiKey: process.env.OPENAI_API_KEY }); @@ -388,7 +388,7 @@ Two providers rank by meaning, with the same options: - `inMemoryVectorMemory({ embedder, maxItems?, minScore? })` keeps items in this process. - `sqliteVectorMemory(store, { embedder, maxItems?, minScore? })` (from - `lousho/sqlite`) keeps them in the agent's SQLite file, in a + `@linuxdevil/lousho/sqlite`) keeps them in the agent's SQLite file, in a `memory_vectors` table that an existing file gains when it is opened. Vectors survive restarts. @@ -412,14 +412,14 @@ re-embeds them in batches and returns how many it changed. `reindex(providerKey)` does one key only — for a slot's key, pass `memoryKey(slot, ctx)`. -`hashEmbedder()` from `lousho/testing` is a deterministic +`hashEmbedder()` from `@linuxdevil/lousho/testing` is a deterministic offline embedder for tests: a hashed bag of word stems, so texts that share words are close. It is not a semantic model and does not know that "vegetarian" and "eat" are related. ```ts -import { inMemoryVectorMemory } from 'lousho'; -import { hashEmbedder } from 'lousho/testing'; +import { inMemoryVectorMemory } from '@linuxdevil/lousho'; +import { hashEmbedder } from '@linuxdevil/lousho/testing'; const memory = inMemoryVectorMemory({ embedder: hashEmbedder() }); await memory.add('global', { text: 'Sam is vegetarian' }); diff --git a/docs/migrating-from-ai-sdk.md b/docs/migrating-from-ai-sdk.md index 2469ef74b..ced133f23 100644 --- a/docs/migrating-from-ai-sdk.md +++ b/docs/migrating-from-ai-sdk.md @@ -41,7 +41,7 @@ you already configured (keys, base URL, provider options) works as it is: ```ts no-run import { createOpenAI } from '@ai-sdk/openai'; -import { createAgent, fromAiSdk } from 'lousho'; +import { createAgent, fromAiSdk } from '@linuxdevil/lousho'; const openai = createOpenAI({ apiKey: process.env.OPENAI_API_KEY }); const agent = createAgent({ provider: fromAiSdk(openai.chat('gpt-4o-mini')), instructions: 'You are helpful.' }); @@ -59,7 +59,7 @@ See [Providers](./providers.md#any-ai-sdk-model-fromaisdk) for the options and w ```ts no-verify import { tool } from 'ai'; import { z } from 'zod'; -import { createAgent, fromAiSdk, fromAiSdkTools } from 'lousho'; +import { createAgent, fromAiSdk, fromAiSdkTools } from '@linuxdevil/lousho'; const weather = tool({ description: 'Get the weather in a city', @@ -85,8 +85,8 @@ and a plain object works too. This one runs offline: ```ts import { z } from 'zod'; -import { createAgent, fromAiSdkTools } from 'lousho'; -import { mockModel } from 'lousho/testing'; +import { createAgent, fromAiSdkTools } from '@linuxdevil/lousho'; +import { mockModel } from '@linuxdevil/lousho/testing'; const tools = fromAiSdkTools({ weather: { @@ -128,7 +128,7 @@ A `useChat` front end keeps working: the route returns the agent's run as a UI m stream. ```ts no-run -import { createAgent, fromUIMessages, memoryStore, toUIMessageStreamResponse, type UIMessageLike } from 'lousho'; +import { createAgent, fromUIMessages, memoryStore, toUIMessageStreamResponse, type UIMessageLike } from '@linuxdevil/lousho'; const agent = createAgent({ model: 'openai/gpt-4o-mini', instructions: 'You are helpful.', store: memoryStore() }); diff --git a/docs/migrating-to-create-agent.md b/docs/migrating-to-create-agent.md index 732917fe3..ff8a511d0 100644 --- a/docs/migrating-to-create-agent.md +++ b/docs/migrating-to-create-agent.md @@ -2,7 +2,7 @@ This page is for code that uses `AgentBuilder`, `AgentExecutor`, `ToolRegistry` and `resumeAfterApproval()`: the lower-level API the SDK started with. Those exports now -live at `lousho/executor` and are not going away; if you stay on +live at `@linuxdevil/lousho/executor` and are not going away; if you stay on them, see [The executor API](./executor-api.md). The reason to move is that one `createAgent()` call wires tools, stores, approvals, sessions, retries and compaction, where the executor makes you assemble each of them and pass them to every @@ -24,9 +24,9 @@ import { AgentExecutor, ToolRegistry, resumeAfterApproval, -} from 'lousho/executor'; -import { InMemoryApprovalStore, defineTool } from 'lousho'; -import { mockModel } from 'lousho/testing'; +} from '@linuxdevil/lousho/executor'; +import { InMemoryApprovalStore, defineTool } from '@linuxdevil/lousho'; +import { mockModel } from '@linuxdevil/lousho/testing'; import { z } from 'zod'; const sendEmail = defineTool({ @@ -68,8 +68,8 @@ console.log(result.text); // 'Sent the report to Sam.' ``` ```ts -import { createAgent, defineTool } from 'lousho'; -import { mockModel } from 'lousho/testing'; +import { createAgent, defineTool } from '@linuxdevil/lousho'; +import { mockModel } from '@linuxdevil/lousho/testing'; import { z } from 'zod'; const sendEmail = defineTool({ diff --git a/docs/models-and-cost.md b/docs/models-and-cost.md index 4aa6d6f65..fd876357f 100644 --- a/docs/models-and-cost.md +++ b/docs/models-and-cost.md @@ -6,7 +6,7 @@ Dependency-free helpers for budgeting and context decisions. ```ts -import { estimateTokens, estimateCost, getModelInfo, registerModel } from 'lousho'; +import { estimateTokens, estimateCost, getModelInfo, registerModel } from '@linuxdevil/lousho'; // A custom or self-hosted model: add it (or override a built-in) before use. registerModel({ @@ -36,7 +36,7 @@ The built-in context windows and prices are a dated snapshot (see the retrieval Every `ExecutionResult` (and `agent.send()` result) carries `usage`, the running total of the whole run: ```ts -import { createAgent, formatUsage } from 'lousho'; +import { createAgent, formatUsage } from '@linuxdevil/lousho'; const result = await createAgent({ provider, instructions: 'You compare cities.' }).send('Compare 3 cities'); @@ -61,7 +61,7 @@ console.log(formatUsage(result.usage)); // 1,234 in / 567 out tokens · $0.0042 Prices come from the model registry above, so to get a cost for a custom or fine-tuned model, register it under the id you pass as the model: ```ts -import { registerModel } from 'lousho'; +import { registerModel } from '@linuxdevil/lousho'; registerModel({ id: 'ft:gpt-4o-mini:acme', @@ -82,7 +82,7 @@ Anthropic caches a request prefix only up to a `cache_control` breakpoint that t ```ts import { readFileSync } from 'node:fs'; -import { createAgent } from 'lousho'; +import { createAgent } from '@linuxdevil/lousho'; const longPolicy = readFileSync('./policy.md', 'utf8'); // a long, stable system prompt const agent = createAgent({ model: 'openrouter/anthropic/claude-haiku-4.5', instructions: longPolicy }); // promptCaching: 'auto' diff --git a/docs/nextjs.md b/docs/nextjs.md index 1d71e66db..352291df7 100644 --- a/docs/nextjs.md +++ b/docs/nextjs.md @@ -13,7 +13,7 @@ prefix chooses the provider; with OpenRouter use `openrouter//` ```ts // app/api/agent/[[...path]]/route.ts -import { createAgent, createRouteHandler, memoryStore } from 'lousho'; +import { createAgent, createRouteHandler, memoryStore } from '@linuxdevil/lousho'; // `store` is what lets a session survive across requests: without one every // `agent.session({ id })` the handler opens gets a fresh in-memory transcript, @@ -36,7 +36,7 @@ and spend your model budget. Pass a bearer token, or a function that checks your own session or cookie: ```ts -import { createAgent, createRouteHandler } from 'lousho'; +import { createAgent, createRouteHandler } from '@linuxdevil/lousho'; declare function isSignedIn(request: Request): Promise; @@ -89,8 +89,8 @@ customer's session (`GET /chat/:id`, to show what they are approving) and to decide in it; by default the session belongs to the customer alone: ```ts -import { createAgent, createRouteHandler, memoryStore } from 'lousho'; -import { jwt } from 'lousho/auth'; +import { createAgent, createRouteHandler, memoryStore } from '@linuxdevil/lousho'; +import { jwt } from '@linuxdevil/lousho/auth'; const agent = createAgent({ model: 'openai/gpt-4o-mini', store: memoryStore() }); const isSupervisor = (principal?: { claims?: Readonly> }) => principal?.claims?.role === 'supervisor'; @@ -131,7 +131,7 @@ see [Route auth and principals](./auth.md). ```tsx no-verify 'use client'; -import { useLoushoAgent } from 'lousho/react'; +import { useLoushoAgent } from '@linuxdevil/lousho/react'; export function Chat() { const agent = useLoushoAgent({ url: '/api/agent', headers: { Authorization: `Bearer ${token}` } }, { approvalsUrl: '/api/agent/approvals' }); @@ -158,7 +158,7 @@ const { messages, sendMessage } = useChat({ transport: new DefaultChatTransport( The same handler, mounted where the framework wants it: ```ts -import { createAgent, createRouteHandler } from 'lousho'; +import { createAgent, createRouteHandler } from '@linuxdevil/lousho'; const { handler } = createRouteHandler(createAgent({ model: 'openai/gpt-4o-mini' }), { basePath: '/agent', auth: 'secret' }); diff --git a/docs/oauth.md b/docs/oauth.md index eb5c96d2c..99ddaaf6f 100644 --- a/docs/oauth.md +++ b/docs/oauth.md @@ -38,7 +38,7 @@ token, an operator who knows which authenticator the old tokens belong to moves them once: ```ts -import { fileStore } from 'lousho'; +import { fileStore } from '@linuxdevil/lousho'; const store = fileStore('./.lousho', { tokenKey: process.env.LOUSHO_TOKEN_KEY }); const legacy = { owner: 'user', principalId: 'U04ABCDEF' } as const; @@ -60,7 +60,7 @@ key unambiguous: a principal id containing `|` cannot collide with another owner. ```ts -import { tokenStoreKey } from 'lousho'; +import { tokenStoreKey } from '@linuxdevil/lousho'; tokenStoreKey('github', { owner: 'app' }); // 'github|app' tokenStoreKey('github', { owner: 'user', principalId: 'a|b', issuer: 'https://id.example.com' }); @@ -72,7 +72,7 @@ tokenStoreKey('github', { owner: 'user', principalId: 'a|b', issuer: 'https://id `AgentStore.tokens` is an `OAuthTokenStore`: ```ts -import { memoryStore } from 'lousho'; +import { memoryStore } from '@linuxdevil/lousho'; const { tokens } = memoryStore(); const alice = { owner: 'user', principalId: 'alice', issuer: 'https://id.example.com' } as const; @@ -115,7 +115,7 @@ node -e "console.log(Buffer.from(crypto.getRandomValues(new Uint8Array(32))).toS ``` ```ts -import { createAgent, fileStore, generateTokenKey } from 'lousho'; +import { createAgent, fileStore, generateTokenKey } from '@linuxdevil/lousho'; console.log(generateTokenKey()); // store it as a secret, e.g. LOUSHO_TOKEN_KEY @@ -171,7 +171,7 @@ Define the provider once, at module level next to the tools that use it, with `defineOAuthProvider()`. A tool asks for a token with `ctx.getToken(provider)`: ```ts -import { createAgent, defineOAuthProvider, defineTool, memoryStore } from 'lousho'; +import { createAgent, defineOAuthProvider, defineTool, memoryStore } from '@linuxdevil/lousho'; import { z } from 'zod'; export const github = defineOAuthProvider({ @@ -236,7 +236,7 @@ the agent's credential for every user: without the token the call fails with The operator signs the app in once with `agent.oauth.signInUrl(provider)`: ```ts -import { createAgent, defineOAuthProvider, fileStore } from 'lousho'; +import { createAgent, defineOAuthProvider, fileStore } from '@linuxdevil/lousho'; const slackBot = defineOAuthProvider({ name: 'slack_bot', @@ -319,7 +319,7 @@ link continues the run by approving the pause. Serving your own route, do the same: ```ts -import { createAgent } from 'lousho'; +import { createAgent } from '@linuxdevil/lousho'; const agent = createAgent({ provider }); @@ -393,8 +393,8 @@ token store (encrypted by the persistent stores) under the provider name one. ```ts -import { createAgent } from 'lousho'; -import { SqliteStore } from 'lousho/sqlite'; +import { createAgent } from '@linuxdevil/lousho'; +import { SqliteStore } from '@linuxdevil/lousho/sqlite'; const agent = createAgent({ provider, diff --git a/docs/observability.md b/docs/observability.md index 43b785168..0551b8673 100644 --- a/docs/observability.md +++ b/docs/observability.md @@ -19,14 +19,14 @@ mapping. Pass an `exporter` to `createAgent()`; every run of the agent (`send()`, `stream()`, session turns, `resume()` and runs continued by `agent.approvals.resolve()`) is traced, sub-agents included. To keep traces -on disk and browse them with `npx lousho traces`, use `fileTraceExporter()` +on disk and browse them with `npx @linuxdevil/lousho traces`, use `fileTraceExporter()` (see [Local traces](#local-traces)). The `vendor/` model prefix chooses the provider; with OpenRouter use `openrouter//` (e.g. `openrouter/openai/gpt-4o-mini`). ```ts -import { createAgent } from 'lousho'; -import { fileTraceExporter } from 'lousho/traces'; +import { createAgent } from '@linuxdevil/lousho'; +import { fileTraceExporter } from '@linuxdevil/lousho/traces'; const agent = createAgent({ model: 'openai/gpt-4o-mini', @@ -44,8 +44,8 @@ reported once per exporter with `console.warn` and otherwise ignored. With the executor, pass the exporter to `AgentExecutor.execute()`: ```ts -import { AgentExecutor } from 'lousho/executor'; -import { type TraceExporter } from 'lousho'; +import { AgentExecutor } from '@linuxdevil/lousho/executor'; +import { type TraceExporter } from '@linuxdevil/lousho'; const exporter: TraceExporter = { onSpanStart: (span) => console.log('[start]', span.kind, span.name), @@ -56,7 +56,7 @@ await AgentExecutor.execute({ agent, input, provider, toolRegistry, exporter }); ``` For real OpenTelemetry spans, import `createOtelTraceExporter()` from -`lousho/otel` (needs the optional peer dependency +`@linuxdevil/lousho/otel` (needs the optional peer dependency `@opentelemetry/api` and a registered `TracerProvider`). It carries span kind and error status over to OpenTelemetry. See `examples/tracing` (`npm run example:tracing:console`, `npm run example:tracing:otel`, which also prints metrics). @@ -84,7 +84,7 @@ To put several runs in one trace, pass a span id of yours as `parentSpanId`; the run's `invoke_agent` span becomes its child, and its cost rolls up into it: ```ts -import { withSpan, type SimpleAgent, type TraceExporter } from 'lousho'; +import { withSpan, type SimpleAgent, type TraceExporter } from '@linuxdevil/lousho'; declare const exporter: TraceExporter; declare const planner: SimpleAgent; @@ -251,7 +251,7 @@ duration metric, only the model and tool calls inside them. ```ts import { metrics } from '@opentelemetry/api'; import { ConsoleMetricExporter, MeterProvider, PeriodicExportingMetricReader } from '@opentelemetry/sdk-metrics'; -import { createOtelTraceExporter } from 'lousho/otel'; +import { createOtelTraceExporter } from '@linuxdevil/lousho/otel'; // Swap ConsoleMetricExporter for an OTLP exporter to reach any backend. metrics.setGlobalMeterProvider( @@ -321,7 +321,7 @@ piece of plain text (before JSON encoding) and returns what to record; if it throws, that text is recorded as `[redacted]` and the run carries on: ```ts -import { createAgent } from 'lousho'; +import { createAgent } from '@linuxdevil/lousho'; createAgent({ model: 'openai/gpt-4o-mini', @@ -337,8 +337,8 @@ createAgent({ With the executor, opt in per run: ```ts -import { AgentExecutor } from 'lousho/executor'; -import { type TraceExporter } from 'lousho'; +import { AgentExecutor } from '@linuxdevil/lousho/executor'; +import { type TraceExporter } from '@linuxdevil/lousho'; declare const exporter: TraceExporter; @@ -394,13 +394,13 @@ governs only the `gen_ai.*` content attributes. ## Local traces -`fileTraceExporter()` from `lousho/traces` writes each run to a +`fileTraceExporter()` from `@linuxdevil/lousho/traces` writes each run to a file, and `lousho traces` shows the files in the terminal. No OpenTelemetry backend, no extra package. ```ts -import { createAgent, defineTool } from 'lousho'; -import { fileTraceExporter } from 'lousho/traces'; +import { createAgent, defineTool } from '@linuxdevil/lousho'; +import { fileTraceExporter } from '@linuxdevil/lousho/traces'; import { z } from 'zod'; const getWeather = defineTool({ @@ -420,10 +420,10 @@ const agent = createAgent({ Then, from the project directory: ```bash -npx lousho traces # the 20 most recent runs, newest first -npx lousho traces --limit 50 --json -npx lousho traces 3f2a # one run as a tree; a unique id prefix is enough -npx lousho traces 3f2a --content # also the captured messages and tool arguments +npx @linuxdevil/lousho traces # the 20 most recent runs, newest first +npx @linuxdevil/lousho traces --limit 50 --json +npx @linuxdevil/lousho traces 3f2a # one run as a tree; a unique id prefix is enough +npx @linuxdevil/lousho traces 3f2a --content # also the captured messages and tool arguments ``` ``` @@ -506,7 +506,7 @@ check comes first. One SQLite file (default `~/.lousho/observe.db`, WAL mode) holds the fleet's observations, sessions, eval scores, prompt versions and shared context. -`npx lousho studio` renders it (see [Agent Forge](./agent-forge.md#screens)): +`npx @linuxdevil/lousho studio` renders it (see [Agent Forge](./agent-forge.md#screens)): Traces and Trace (trace trees with latency waterfalls), Threads, Monitoring (a live fleet monitor with heartbeat-derived status — a crashed agent's open sessions flip to `interrupted` — over per-agent charts of runs, p95 latency, diff --git a/docs/openapi-tools.md b/docs/openapi-tools.md index cb8f545be..21c2f3ce4 100644 --- a/docs/openapi-tools.md +++ b/docs/openapi-tools.md @@ -6,15 +6,15 @@ the API without a hand-written `defineTool()` per endpoint and without the raw [`http` tool](./tools.md#built-in-tools). Operations that change data ask for [approval](./approvals.md) by default. -`openApiTools` is exported from `lousho` and -`lousho/tools`. It needs no extra package. The `vendor/` model +`openApiTools` is exported from `@linuxdevil/lousho` and +`@linuxdevil/lousho/tools`. It needs no extra package. The `vendor/` model prefix chooses the provider; with OpenRouter use `openrouter//` (e.g. `openrouter/openai/gpt-4o-mini`). ## Quick start ```ts no-run -import { createAgent, openApiTools } from 'lousho'; +import { createAgent, openApiTools } from '@linuxdevil/lousho'; const tools = await openApiTools('https://api.example.com/openapi.json', { include: ['getOrder', 'refundOrder'], @@ -75,7 +75,7 @@ destructiveHint: false` on GET, HEAD and OPTIONS; `readOnlyHint: false` on the rest, with `destructiveHint: true` for DELETE. Anything that reads annotations treats these tools the way it treats MCP tools. ```ts no-run -import { openApiTools } from 'lousho'; +import { openApiTools } from '@linuxdevil/lousho'; declare const spec: object; @@ -93,7 +93,7 @@ never sees it) and added to the request. The value can be a function, called per request with the operation and the tool call id. ```ts no-run -import { openApiTools } from 'lousho'; +import { openApiTools } from '@linuxdevil/lousho'; declare const spec: object; declare const currentTenant: () => string; @@ -164,7 +164,7 @@ An API with hundreds of operations is hundreds of tools. Give the agent the ones it needs with `include` or `exclude`, or with a predicate on `op.tags`: ```ts no-run -import { openApiTools } from 'lousho'; +import { openApiTools } from '@linuxdevil/lousho'; declare const spec: object; diff --git a/docs/pause-and-resume.md b/docs/pause-and-resume.md index e40149621..3e13344b5 100644 --- a/docs/pause-and-resume.md +++ b/docs/pause-and-resume.md @@ -13,7 +13,7 @@ section links to the page that has the full detail. Set `needsApproval` on the tool. The run pauses before the tool executes: ```ts -import { defineTool } from 'lousho'; +import { defineTool } from '@linuxdevil/lousho'; import { z } from 'zod'; export const refund = defineTool({ @@ -41,9 +41,9 @@ shared by several processes on one host) or `fileStore(dir)` (plain JSON files). `finishReason: 'awaiting-approval'` and the id to decide: ```ts -import { createAgent, defineTool } from 'lousho'; -import { SqliteStore } from 'lousho/sqlite'; -import { mockModel } from 'lousho/testing'; +import { createAgent, defineTool } from '@linuxdevil/lousho'; +import { SqliteStore } from '@linuxdevil/lousho/sqlite'; +import { mockModel } from '@linuxdevil/lousho/testing'; import { z } from 'zod'; const refund = defineTool({ @@ -78,9 +78,9 @@ run left in the store. `agent.approvals.resolve()` runs the call (or rejects it) continues the run, and resolves with the result: ```ts -import { createAgent, defineTool } from 'lousho'; -import { SqliteStore } from 'lousho/sqlite'; -import { mockModel } from 'lousho/testing'; +import { createAgent, defineTool } from '@linuxdevil/lousho'; +import { SqliteStore } from '@linuxdevil/lousho/sqlite'; +import { mockModel } from '@linuxdevil/lousho/testing'; import { z } from 'zod'; const refund = defineTool({ @@ -135,8 +135,8 @@ This snippet stands in for the crash with a tool that throws step), then recovers: ```ts -import { createAgent, defineTool, memoryStore, PropagatingToolError } from 'lousho'; -import { mockModel } from 'lousho/testing'; +import { createAgent, defineTool, memoryStore, PropagatingToolError } from '@linuxdevil/lousho'; +import { mockModel } from '@linuxdevil/lousho/testing'; import { z } from 'zod'; let charges = 0; diff --git a/docs/permission-modes.md b/docs/permission-modes.md index 26209dbec..cad1801fa 100644 --- a/docs/permission-modes.md +++ b/docs/permission-modes.md @@ -6,8 +6,8 @@ pause (`dontAsk`). Switch it in the middle of a session, the way you would work with a coding agent: plan first, then let it edit. ```ts -import { createAgent, createFsTools, MemoryWorkspace } from 'lousho'; -import { mockModel } from 'lousho/testing'; +import { createAgent, createFsTools, MemoryWorkspace } from '@linuxdevil/lousho'; +import { mockModel } from '@linuxdevil/lousho/testing'; const workspace = new MemoryWorkspace({ files: { 'notes.md': '# Notes\n' } }); const agent = createAgent({ @@ -73,7 +73,7 @@ says nothing is treated as one with side effects and is refused. Declare a custom read-only tool like this: ```ts -import { defineTool } from 'lousho'; +import { defineTool } from '@linuxdevil/lousho'; import { z } from 'zod'; const lookUpOrder = defineTool({ @@ -95,7 +95,7 @@ The hint is a promise you make about your tool: plan mode trusts it. alone, so a tool of your own named `write_file` is not approved silently. ```ts -import { defineTool } from 'lousho'; +import { defineTool } from '@linuxdevil/lousho'; import { z } from 'zod'; const appendLine = defineTool({ @@ -140,7 +140,7 @@ Switching is always an explicit call to `setPermissionMode()`. Each switch is reported to `onPermissionModeChange` with `{ sessionId, from, to, at }`: ```ts -import { createAgent } from 'lousho'; +import { createAgent } from '@linuxdevil/lousho'; const agent = createAgent({ provider, diff --git a/docs/prompting-techniques.md b/docs/prompting-techniques.md index 9a2b1145a..87427ede1 100644 --- a/docs/prompting-techniques.md +++ b/docs/prompting-techniques.md @@ -62,8 +62,8 @@ The four operations, and where each lives in the SDK: | **Isolate** | Run work in a clean context so detail doesn't flood the lead | Sub-agents (`task` calls see only their prompt), code mode (`run_code` returns one value), the `inputFilter` boundary | ```ts no-run -import { createAgent, loadSkills } from 'lousho'; -import { mockModel } from 'lousho/testing'; +import { createAgent, loadSkills } from '@linuxdevil/lousho'; +import { mockModel } from '@linuxdevil/lousho/testing'; const provider = mockModel([{ text: 'done' }]); const researchAgent = createAgent({ provider, instructions: 'You research one slice.' }); diff --git a/docs/providers.md b/docs/providers.md index 309baac9f..5e0219785 100644 --- a/docs/providers.md +++ b/docs/providers.md @@ -9,7 +9,7 @@ OpenRouter use `openrouter//` (e.g. `openrouter/openai/gpt-4o-mini`). ```ts -import { resolveProvider, LLMProviderRegistry } from 'lousho'; +import { resolveProvider, LLMProviderRegistry } from '@linuxdevil/lousho'; // The convenient way — reads the credential from the environment const openai = resolveProvider('openai/gpt-4o-mini'); // OPENAI_API_KEY @@ -39,7 +39,7 @@ the vendor's package (`^1.0.0` with `ai` 4, `^3.0.0` with `ai` 6, `^4.0.0` with (`MISTRAL_API_KEY`, `GROQ_API_KEY`, the AWS chain for Bedrock, and so on). ```ts no-run -import { createAgent } from 'lousho'; +import { createAgent } from '@linuxdevil/lousho'; createAgent({ model: 'mistral/mistral-large-latest' }); // @ai-sdk/mistral createAgent({ model: 'bedrock/anthropic.claude-sonnet' }); // @ai-sdk/amazon-bedrock @@ -89,7 +89,7 @@ it anyway: present. With none set it throws an error listing the fixes. ```ts no-run -import { createAgent, createMockProvider, resolveProvider } from 'lousho'; +import { createAgent, createMockProvider, resolveProvider } from '@linuxdevil/lousho'; const fromString = createAgent({ model: 'anthropic/claude-sonnet-5-5' }); const fromInstance = createAgent({ provider: resolveProvider('openai/gpt-4o-mini'), model: 'gpt-4o' }); @@ -107,7 +107,7 @@ error, 5xx) twice by default, and with `fallbackModels` moves on to the next model when the call still fails: ```ts -import { createAgent } from 'lousho'; +import { createAgent } from '@linuxdevil/lousho'; const agent = createAgent({ model: 'openai/gpt-4o-mini', @@ -150,7 +150,7 @@ Parts sent through `createAgent()`: ```ts import { readFileSync } from 'node:fs'; -import { createAgent } from 'lousho'; +import { createAgent } from '@linuxdevil/lousho'; const agent = createAgent({ instructions: 'Describe images briefly.', model: 'openai/gpt-4o-mini' }); @@ -166,7 +166,7 @@ console.log(answer.text); A `Message[]` is passed through as it is; `textOf()` returns the text parts of a message: ```ts -import { createAgent, textOf, type Message } from 'lousho'; +import { createAgent, textOf, type Message } from '@linuxdevil/lousho'; const agent = createAgent({ instructions: 'Describe images briefly.', model: 'openai/gpt-4o-mini' }); const messages: Message[] = [ @@ -225,8 +225,8 @@ console.log(textOf(messages[0]), '->', result.text); `AgentExecutor.execute()` takes the same messages as its `input` (see [the executor API](./executor-api.md)): ```ts -import { AgentBuilder, AgentExecutor } from 'lousho/executor'; -import { resolveProvider, type Message } from 'lousho'; +import { AgentBuilder, AgentExecutor } from '@linuxdevil/lousho/executor'; +import { resolveProvider, type Message } from '@linuxdevil/lousho'; const agent = AgentBuilder.create().setName('vision').setPrompt('Describe images briefly.').build(); const input: Message[] = [ @@ -288,7 +288,7 @@ text is not retried, so it fails after one `timeout`. To fail sooner, lower `timeout` or `retry.maxRetries`. ```ts -import { OpenAIProvider } from 'lousho'; +import { OpenAIProvider } from '@linuxdevil/lousho'; const provider = new OpenAIProvider({ apiKey: process.env.OPENAI_API_KEY!, timeout: 60_000 }); ``` @@ -302,7 +302,7 @@ Pass `store: true` to have OpenAI store them (for its dashboard logs or evals). `store` is ignored with `api: 'chat'`. ```ts -import { OpenAIProvider } from 'lousho'; +import { OpenAIProvider } from '@linuxdevil/lousho'; const provider = new OpenAIProvider({ apiKey: process.env.OPENAI_API_KEY!, store: true }); ``` @@ -319,7 +319,7 @@ instead. Install `undici` and pass its `Agent` as the dispatcher: ```ts import { Agent } from 'undici'; -import { createAgent, OpenAIProvider } from 'lousho'; +import { createAgent, OpenAIProvider } from '@linuxdevil/lousho'; // Wait up to 15 minutes for the response headers and between body chunks. const dispatcher = new Agent({ headersTimeout: 15 * 60_000, bodyTimeout: 15 * 60_000 }); @@ -347,7 +347,7 @@ your function, so a failure you return from it is classified as it would be from the real network: ```ts -import { OpenRouterProvider } from 'lousho'; +import { OpenRouterProvider } from '@linuxdevil/lousho'; let failures = 1; const provider = new OpenRouterProvider({ @@ -365,7 +365,7 @@ into an `LLMProvider`, so you do not write an adapter: ```ts import { createOpenAI } from '@ai-sdk/openai'; -import { createAgent, fromAiSdk } from 'lousho'; +import { createAgent, fromAiSdk } from '@linuxdevil/lousho'; const openai = createOpenAI({ apiKey: process.env.OPENAI_API_KEY }); const agent = createAgent({ @@ -416,7 +416,7 @@ npm install @earendil-works/pi-ai@^1.0.3 ``` ```ts -import { createAgent } from 'lousho'; +import { createAgent } from '@linuxdevil/lousho'; // OpenRouter's `openai/gpt-4o-mini`, through pi's openrouter adapter: const agent = createAgent({ model: 'pi/openrouter/openai/gpt-4o-mini' }); @@ -462,7 +462,7 @@ wire format. Point `OpenAIProvider` at the server's `/v1` URL and pick the API it implements with `api`: ```ts no-run -import { createAgent, OpenAIProvider } from 'lousho'; +import { createAgent, OpenAIProvider } from '@linuxdevil/lousho'; const agent = createAgent({ provider: new OpenAIProvider({ @@ -515,14 +515,14 @@ Ollama's `/v1` need). ```ts no-run import { createOpenAI } from '@ai-sdk/openai'; -import { createAgent, fromAiSdk } from 'lousho'; +import { createAgent, fromAiSdk } from '@linuxdevil/lousho'; const local = createOpenAI({ baseURL: 'http://localhost:8080/v1', apiKey: 'any' }); const agent = createAgent({ provider: fromAiSdk(local.chat('qwen3-8b')), instructions: 'You are a helpful assistant.' }); ``` -`npx lousho doctor` shows `OPENAI_BASE_URL` when it is set, and -`npx lousho doctor --ping` calls `GET /models` on each configured +`npx @linuxdevil/lousho doctor` shows `OPENAI_BASE_URL` when it is set, and +`npx @linuxdevil/lousho doctor --ping` calls `GET /models` on each configured provider to check that the server answers. ## Vercel AI SDK versions diff --git a/docs/queue-and-steer.md b/docs/queue-and-steer.md index 3624f5756..02c7ce196 100644 --- a/docs/queue-and-steer.md +++ b/docs/queue-and-steer.md @@ -15,7 +15,7 @@ reply gets one more step, so the model answers it in the same run. The `openrouter//` (e.g. `openrouter/openai/gpt-4o-mini`). ```ts -import { createAgent } from 'lousho'; +import { createAgent } from '@linuxdevil/lousho'; const agent = createAgent({ model: 'openai/gpt-4o-mini' }); const run = agent.stream('Plan a weekend in Rome.'); @@ -62,8 +62,8 @@ Without a stream, pass an `InputQueue` to `AgentExecutor.execute()` (see [the executor API](./executor-api.md)): ```ts -import { AgentExecutor } from 'lousho/executor'; -import { InputQueue } from 'lousho'; +import { AgentExecutor } from '@linuxdevil/lousho/executor'; +import { InputQueue } from '@linuxdevil/lousho'; const inputQueue = new InputQueue(); const pending = AgentExecutor.execute({ agent, provider, input: 'Plan my trip.', inputQueue }); @@ -90,7 +90,7 @@ the user changes their mind while the agent is still thinking: - `applied` is `false` once the run has finished: send the input as a new turn. ```ts -import { createAgent } from 'lousho'; +import { createAgent } from '@linuxdevil/lousho'; const agent = createAgent({ model: 'openai/gpt-4o-mini' }); const run = agent.stream('Plan a weekend in Rome.'); diff --git a/docs/quick-start.md b/docs/quick-start.md index 424d30df9..93e5b37cb 100644 --- a/docs/quick-start.md +++ b/docs/quick-start.md @@ -20,19 +20,19 @@ npm test # offline tests: no API key needed To try a locally packed `create-lousho` (`npm pack`), pass the tarball's absolute path with `--package`: `npx --package=/abs/path/create-lousho-x.y.z.tgz create-lousho my-agent`. A bare `npx ../x.tgz` fails on Windows. -`npx lousho init my-agent` is equivalent (pass `--provider` +`npx @linuxdevil/lousho init my-agent` is equivalent (pass `--provider` and `--template`, since it runs without the interactive prompts). Inside a -project that already has the SDK installed, `npx lousho init` works too: the -`lousho` command comes from the SDK package, so a bare `npx lousho` outside such +project that already has the SDK installed, `npx @linuxdevil/lousho init` works too: the +`lousho` command comes from the SDK package, so a bare `npx @linuxdevil/lousho` outside such a project finds nothing to run. Without arguments, `init` asks for the -directory, provider and template; `npx lousho init --help` +directory, provider and template; `npx @linuxdevil/lousho init --help` lists every flag. Prefer to add the SDK to an existing project? Install it by hand (see [Installation](./installation.md)): ```bash -npm install lousho ai@^7.0.0 zod +npm install @linuxdevil/lousho ai@^7.0.0 zod npm install @ai-sdk/openai@^4.0.0 @ai-sdk/anthropic@^4.0.0 ``` @@ -49,7 +49,7 @@ providers). The `vendor/` model prefix chooses the provider; with OpenRouter use `openrouter//` (e.g. `openrouter/openai/gpt-4.1-mini`). ```ts no-run -import { createAgent } from 'lousho'; +import { createAgent } from '@linuxdevil/lousho'; const agent = createAgent({ model: 'openai/gpt-4.1-mini', instructions: 'You are a helpful assistant.' }); const { text } = await agent.send('Hello!'); @@ -72,7 +72,7 @@ whenever the user message mentions a tool's name, so this snippet exercises a real tool round trip without an LLM. ```ts -import { createAgent, createMockProvider, defineTool } from 'lousho'; +import { createAgent, createMockProvider, defineTool } from '@linuxdevil/lousho'; import { z } from 'zod'; const currentDate = defineTool({ @@ -105,7 +105,7 @@ it for typed events (model text as it arrives, tool calls, step boundaries), and `await run.result` for the same result `send()` returns. ```ts -import { createAgent, createMockProvider } from 'lousho'; +import { createAgent, createMockProvider } from '@linuxdevil/lousho'; const agent = createAgent({ instructions: 'You are a helpful assistant.', @@ -131,7 +131,7 @@ sending the events to a browser. keeps the transcript and passes it to the model on each `send()`. ```ts -import { createAgent, createMockProvider } from 'lousho'; +import { createAgent, createMockProvider } from '@linuxdevil/lousho'; const agent = createAgent({ instructions: 'You are a helpful assistant.', @@ -153,12 +153,12 @@ Pass a store to keep sessions across restarts; see [Sessions](./sessions.md). Flag a tool `needsApproval` and the run pauses before calling it. `send()` resolves with `finishReason: 'awaiting-approval'` and an `approvalId`; your code (or a human) then approves or rejects the call and the run continues. Here -`mockModel` from `lousho/testing` scripts the model's turns +`mockModel` from `@linuxdevil/lousho/testing` scripts the model's turns exactly, so the tool call is certain. ```ts -import { createAgent, defineTool } from 'lousho'; -import { mockModel } from 'lousho/testing'; +import { createAgent, defineTool } from '@linuxdevil/lousho'; +import { mockModel } from '@linuxdevil/lousho/testing'; import { z } from 'zod'; const sendEmail = defineTool({ @@ -196,8 +196,8 @@ request the agent made, so you can assert on what the model was sent as well as on the result. No network, no key. ```ts -import { createAgent, defineTool } from 'lousho'; -import { mockModel } from 'lousho/testing'; +import { createAgent, defineTool } from '@linuxdevil/lousho'; +import { mockModel } from '@linuxdevil/lousho/testing'; import { z } from 'zod'; let sent: string | undefined; @@ -244,7 +244,7 @@ the mock provider otherwise - the same pattern the runnable [examples](../examples/README.md) use. ```ts -import { createAgent, createMockProvider, resolveProvider } from 'lousho'; +import { createAgent, createMockProvider, resolveProvider } from '@linuxdevil/lousho'; const provider = process.env.OPENAI_API_KEY ? resolveProvider('openai/gpt-4.1-mini') @@ -267,7 +267,7 @@ into a live agent with `specToAgent()`. The same shape can be written as a YAML or JSON file and loaded with `loadSpec()`. ```ts -import { specToAgent, agentSpecSchema } from 'lousho'; +import { specToAgent, agentSpecSchema } from '@linuxdevil/lousho'; const spec = agentSpecSchema.parse({ name: 'support-bot', @@ -297,8 +297,8 @@ the same spec runs in the local dev server (chat UI at `/`, `POST /chat`, hot reload on save) and builds into a deployable server: ```bash -npx lousho dev agent.yaml -npx lousho build --target=node-server --agent=agent.yaml +npx @linuxdevil/lousho dev agent.yaml +npx @linuxdevil/lousho build --target=node-server --agent=agent.yaml ``` ## Advanced: the executor API diff --git a/docs/react.md b/docs/react.md index ca6ba16e9..33888534d 100644 --- a/docs/react.md +++ b/docs/react.md @@ -5,11 +5,11 @@ render-ready state: messages with their text and tool calls, a `status` for the composer, and the tool call waiting for approval, if any. -It lives in the `lousho/react` subpath. `react` (18 or 19) is +It lives in the `@linuxdevil/lousho/react` subpath. `react` (18 or 19) is an optional peer dependency: install it in the app that uses the hook. ```tsx -import { useLoushoAgent } from 'lousho/react'; +import { useLoushoAgent } from '@linuxdevil/lousho/react'; export function Chat() { const agent = useLoushoAgent({ url: '/api/agent' }); @@ -120,8 +120,8 @@ model prefix chooses the provider; with OpenRouter use ```ts import { createServer, type IncomingMessage } from 'node:http'; -import { createAgent } from 'lousho'; -import type { ApprovalOutcome } from 'lousho/react'; +import { createAgent } from '@linuxdevil/lousho'; +import type { ApprovalOutcome } from '@linuxdevil/lousho/react'; const agent = createAgent({ model: 'openai/gpt-4o-mini' }); @@ -170,8 +170,8 @@ gets a fresh in-memory transcript and the next turn forgets the first (see The hook takes the agent itself, too. Called from a custom hook: ```ts -import { createAgent } from 'lousho'; -import { useLoushoAgent } from 'lousho/react'; +import { createAgent } from '@linuxdevil/lousho'; +import { useLoushoAgent } from '@linuxdevil/lousho/react'; const agent = createAgent({ model: 'openai/gpt-4o-mini' }); @@ -191,7 +191,7 @@ event)`, a pure reducer over `AgentEvent`s and a few local actions exported from the same subpath, for a custom binding or a non-React client: ```ts -import { initialAgentUIState, parseEventStream, reduceAgentEvents } from 'lousho/react'; +import { initialAgentUIState, parseEventStream, reduceAgentEvents } from '@linuxdevil/lousho/react'; let state = reduceAgentEvents(initialAgentUIState, { type: 'ui.send', input: 'Weather in Paris?' }); const response = await fetch('http://localhost:3000/api/agent', { @@ -219,7 +219,7 @@ chat and the plan. The list carries across turns and is cleared by nothing but a new hook (it is not restored when the page reconnects to an existing session). ```tsx -import { useLoushoAgent, useTodos } from 'lousho/react'; +import { useLoushoAgent, useTodos } from '@linuxdevil/lousho/react'; export function Plan() { const agent = useLoushoAgent({ url: '/api/agent' }); diff --git a/docs/reasoning.md b/docs/reasoning.md index 514ff3462..24685f44f 100644 --- a/docs/reasoning.md +++ b/docs/reasoning.md @@ -7,7 +7,7 @@ as `reasoning.*` events in `agent.stream()`, and as `result.reasoning`. The `openrouter//` (e.g. `openrouter/anthropic/claude-sonnet-4.5`). ```ts -import { createAgent } from 'lousho'; +import { createAgent } from '@linuxdevil/lousho'; const agent = createAgent({ model: 'anthropic/claude-sonnet-4-5', diff --git a/docs/registry.md b/docs/registry.md index ea179c49b..025f7b32d 100644 --- a/docs/registry.md +++ b/docs/registry.md @@ -88,7 +88,7 @@ Each item document is: "files": [ { "path": "tools/web-search.ts", - "content": "import { defineTool } from 'lousho';\nimport { z } from 'zod';\n\nexport default defineTool({\n name: 'web-search',\n description: 'Search the web',\n input: z.object({ query: z.string() }),\n needsApproval: true,\n execute: async ({ query }) => {\n const response = await fetch(`https://api.example.com/search?q=${encodeURIComponent(query)}`, {\n headers: { authorization: `Bearer ${process.env.SEARCH_KEY}` },\n });\n return response.json();\n },\n});\n" + "content": "import { defineTool } from '@linuxdevil/lousho';\nimport { z } from 'zod';\n\nexport default defineTool({\n name: 'web-search',\n description: 'Search the web',\n input: z.object({ query: z.string() }),\n needsApproval: true,\n execute: async ({ query }) => {\n const response = await fetch(`https://api.example.com/search?q=${encodeURIComponent(query)}`, {\n headers: { authorization: `Bearer ${process.env.SEARCH_KEY}` },\n });\n return response.json();\n },\n});\n" } ], "permissions": { diff --git a/docs/runs.md b/docs/runs.md index 0b080977a..a45c12bd8 100644 --- a/docs/runs.md +++ b/docs/runs.md @@ -25,7 +25,7 @@ same reason is on `run.done` in [streaming](streaming.md) (and on `finish`, the deprecated `onEvent` counterpart). ```ts -import { createAgent, createMockProvider } from 'lousho'; +import { createAgent, createMockProvider } from '@linuxdevil/lousho'; const agent = createAgent({ instructions: 'You are helpful.', provider: createMockProvider(), maxSteps: 3 }); const result = await agent.send('Research this thoroughly'); if (result.finishReason === 'max-steps') console.warn(`Gave up after ${result.steps} steps`); @@ -38,7 +38,7 @@ Pass an `AbortSignal` to stop a run: `agent.send(input, { signal })`, `resumeAfterApproval(..., { signal })`. ```ts -import { createAgent, createMockProvider } from 'lousho'; +import { createAgent, createMockProvider } from '@linuxdevil/lousho'; const agent = createAgent({ instructions: 'You are helpful.', provider: createMockProvider() }); const controller = new AbortController(); setTimeout(() => controller.abort(), 5_000); // e.g. from a Stop button @@ -49,7 +49,7 @@ console.log(result.finishReason); // 'aborted' if it was cancelled, else 'stop' For a time limit, use `AbortSignal.timeout(ms)`: ```ts -import { createAgent, createMockProvider } from 'lousho'; +import { createAgent, createMockProvider } from '@linuxdevil/lousho'; const agent = createAgent({ instructions: 'You are helpful.', provider: createMockProvider() }); const result = await agent.send('Summarize this', { signal: AbortSignal.timeout(30_000) }); ``` @@ -101,7 +101,7 @@ for strictly sequential execution, e.g. when your tools share state that is not safe to touch concurrently. ```ts -import { createAgent, createMockProvider, defineTool } from 'lousho'; +import { createAgent, createMockProvider, defineTool } from '@linuxdevil/lousho'; import { z } from 'zod'; const getWeather = defineTool({ diff --git a/docs/schedules.md b/docs/schedules.md index f6869a1cc..562b27415 100644 --- a/docs/schedules.md +++ b/docs/schedules.md @@ -12,7 +12,7 @@ Give a cron expression and exactly one of `prompt` (text sent to the agent as a new turn) or `run` (your own function). ```ts -import { createAgent, createMockProvider, defineSchedule, startSchedules } from 'lousho'; +import { createAgent, createMockProvider, defineSchedule, startSchedules } from '@linuxdevil/lousho'; const agent = createAgent({ instructions: 'You write reports.', provider: createMockProvider() }); @@ -58,7 +58,7 @@ server or a test exits normally. A script that does nothing but run schedules would therefore exit straight away; pass `keepAlive: true` for that: ```ts no-run -import { createAgent, createMockProvider, defineSchedule, startSchedules } from 'lousho'; +import { createAgent, createMockProvider, defineSchedule, startSchedules } from '@linuxdevil/lousho'; const agent = createAgent({ instructions: 'You write reports.', provider: createMockProvider() }); const running = startSchedules(agent, [defineSchedule({ cron: '@hourly', prompt: 'Check the queue.' })], { keepAlive: true }); @@ -84,7 +84,7 @@ claim is best effort across edge locations (see [Durable execution](./durable-execution.md#run-leases)). ```ts -import { createAgent, createMockProvider, defineSchedule, fireSchedule } from 'lousho'; +import { createAgent, createMockProvider, defineSchedule, fireSchedule } from '@linuxdevil/lousho'; const agent = createAgent({ instructions: 'You write reports.', provider: createMockProvider() }); const report = defineSchedule({ name: 'report', cron: '0 9 * * *', prompt: 'Run the daily report.' }); @@ -105,7 +105,7 @@ they show an empty transcript for a schedule id. Read a fire's report from the checkpoint store instead, or continue it with `agent.fork(id)`: ```ts -import { createAgent, createMockProvider, defineSchedule, fireSchedule, memoryStore } from 'lousho'; +import { createAgent, createMockProvider, defineSchedule, fireSchedule, memoryStore } from '@linuxdevil/lousho'; const store = memoryStore(); const agent = createAgent({ instructions: 'You write reports.', provider: createMockProvider(), store }); @@ -131,7 +131,7 @@ my-agent/ `schedules/` loads exactly as before. ```ts -import { createAgent, resolveAgentDir, startSchedules } from 'lousho'; +import { createAgent, resolveAgentDir, startSchedules } from '@linuxdevil/lousho'; const { config, schedules } = await resolveAgentDir('./my-agent'); const agent = createAgent(config); diff --git a/docs/self-healing.md b/docs/self-healing.md index c1b9481a6..891905f52 100644 --- a/docs/self-healing.md +++ b/docs/self-healing.md @@ -26,8 +26,8 @@ each interrupted run from its last checkpoint, so completed steps don't run again. ```ts -import { createAgent, createProgressGuard } from 'lousho'; -import { SqliteStore } from 'lousho/sqlite'; +import { createAgent, createProgressGuard } from '@linuxdevil/lousho'; +import { SqliteStore } from '@linuxdevil/lousho/sqlite'; const agent = createAgent({ provider, @@ -91,7 +91,7 @@ recorded as not retryable, so `agent.recover()` won't resume the run. Each hint, denial and stop is streamed as a `heal.action` event: ```ts -import { createAgent, createProgressGuard } from 'lousho'; +import { createAgent, createProgressGuard } from '@linuxdevil/lousho'; const agent = createAgent({ provider, hooks: [createProgressGuard()] }); @@ -173,8 +173,8 @@ better instructions and skills using failing cases you give it, and returns a proposal without writing anything. ```ts -import { createAgent } from 'lousho'; -import { formatImproveResult, improveAgent, renderTranscript } from 'lousho/improve'; +import { createAgent } from '@linuxdevil/lousho'; +import { formatImproveResult, improveAgent, renderTranscript } from '@linuxdevil/lousho/improve'; const result = await improveAgent({ harness: { instructions: 'You answer from the docs.', skills: {} }, diff --git a/docs/sessions.md b/docs/sessions.md index 9e438f78b..eaf9d5ff9 100644 --- a/docs/sessions.md +++ b/docs/sessions.md @@ -9,7 +9,7 @@ See also: [Pause, restart, resume](./pause-and-resume.md), a short end-to-end tu > Snippets on this page leave some names to you: `provider` is any `LLMProvider` (offline: `createMockProvider()`). ```ts -import { createAgent } from 'lousho'; +import { createAgent } from '@linuxdevil/lousho'; const chat = createAgent({ instructions: 'Be brief.', provider }); @@ -71,7 +71,7 @@ The `vendor/` model prefix chooses the provider; with OpenRouter use `openrouter//` (e.g. `openrouter/openai/gpt-4o-mini`). ```ts -import { createAgent } from 'lousho'; +import { createAgent } from '@linuxdevil/lousho'; const agent = createAgent({ model: 'openai/gpt-4o-mini' }); const session = agent.session({ id: 'user-42', turnPolicy: 'queue' }); @@ -123,7 +123,7 @@ returns the same `AgentRun` (typed events plus a `result` promise, see it. ```ts -import { createAgent } from 'lousho'; +import { createAgent } from '@linuxdevil/lousho'; const chat = createAgent({ instructions: 'Be brief.', provider }); const session = chat.session(); @@ -192,8 +192,8 @@ crash, a failed checkpoint write or a `PropagatingToolError` can then be finished later, in another process: ```ts -import { createAgent } from 'lousho'; -import { SqliteStore } from 'lousho/sqlite'; +import { createAgent } from '@linuxdevil/lousho'; +import { SqliteStore } from '@linuxdevil/lousho/sqlite'; const agent = createAgent({ provider, store: new SqliteStore('./.lousho/agent.db') }); @@ -286,7 +286,7 @@ console.log(finished?.text, await agent.session({ id: 'user-42' }).pending()); / A `SessionStore` keeps transcripts between calls: ```ts -import type { Message } from 'lousho'; +import type { Message } from '@linuxdevil/lousho'; interface SessionStore { load(id: string): Promise; @@ -317,7 +317,7 @@ writer in another process can still slip between, losing one turn. files on Windows and macOS too. ```ts -import { createAgent, FileSessionStore } from 'lousho'; +import { createAgent, FileSessionStore } from '@linuxdevil/lousho'; const agent = createAgent({ provider }); const store = new FileSessionStore('./.lousho/sessions'); @@ -350,7 +350,7 @@ implementation by where the process runs: | In memory (`memoryStore()`) | `MemorySessionStore` | in memory | `InMemoryApprovalStore` | Tests, scripts, one process that never restarts | | Files (`fileStore(dir)`) | `/sessions/.json` | `/checkpoints/`, `/checkpoint-history/` | `/approvals/.json` | One machine, you want plain inspectable files | | SQLite | `store.sessions` | `store.checkpoints` | `store.approvals` | A Node server: one durable, transactional file, shared by several processes on one host (see [Sharing a SQLite file](#sharing-a-sqlite-file)) | -| Cloudflare KV (`KVStore` from `lousho/kv`) | `store.sessions` | `store.checkpoints` | `store.approvals` | Workers deployments (see [Deployment](deployment.md)) | +| Cloudflare KV (`KVStore` from `@linuxdevil/lousho/kv`) | `store.sessions` | `store.checkpoints` | `store.approvals` | Workers deployments (see [Deployment](deployment.md)) | Each one is an `AgentStore` part: pass them together as `createAgent({ store: { sessions, checkpoints, approvals } })`. `memoryStore()`, @@ -364,7 +364,7 @@ another writer or reader has open fails for a moment with `EPERM`, `EACCES` or file still end with one of them (the last rename wins): ```ts -import { createAgent, fileStore } from 'lousho'; +import { createAgent, fileStore } from '@linuxdevil/lousho'; const agent = createAgent({ provider, store: fileStore('./.lousho') }); // ./.lousho/sessions/user-42.json, ./.lousho/checkpoints/..., ./.lousho/approvals/... @@ -401,7 +401,7 @@ session's transcript), and so are pending approvals and OAuth tokens. It resolves with the counts, `{ sessions, checkpoints, checkpointHistory, oauthPending }`: ```ts -import { fileStore } from 'lousho'; +import { fileStore } from '@linuxdevil/lousho'; const store = fileStore('./.lousho'); await store.prune({ olderThanMs: 30 * 24 * 60 * 60 * 1000 }); // a month @@ -416,8 +416,8 @@ generated Worker uses `KVCheckpointStore`, see [Deployment](deployment.md)). re-exported from the root entry, so importing the SDK never loads it): ```ts -import { createAgent } from 'lousho'; -import { SqliteStore } from 'lousho/sqlite'; +import { createAgent } from '@linuxdevil/lousho'; +import { SqliteStore } from '@linuxdevil/lousho/sqlite'; const store = new SqliteStore('./.lousho/agent.db'); // or ':memory:' const agent = createAgent({ provider, store }); // transcripts, per-step checkpoints and approvals @@ -482,8 +482,8 @@ starts a new session from any of them, so you can try another direction without losing the original conversation: ```ts -import { createAgent, defineTool } from 'lousho'; -import { mockModel } from 'lousho/testing'; +import { createAgent, defineTool } from '@linuxdevil/lousho'; +import { mockModel } from '@linuxdevil/lousho/testing'; import { z } from 'zod'; const searchFlights = defineTool({ diff --git a/docs/skills.md b/docs/skills.md index e0f4f98db..2bbdaf478 100644 --- a/docs/skills.md +++ b/docs/skills.md @@ -22,7 +22,7 @@ can correct itself. ## Skills in code ```ts -import { createAgent, defineSkill } from 'lousho'; +import { createAgent, defineSkill } from '@linuxdevil/lousho'; const agent = createAgent({ instructions: 'You are a release engineer.', @@ -91,7 +91,7 @@ Use the imperative mood and link the PR. (both paths are listed). ```ts -import { createAgent, loadSkills } from 'lousho'; +import { createAgent, loadSkills } from '@linuxdevil/lousho'; const agent = createAgent({ instructions: 'You are a release engineer.', diff --git a/docs/stream-events.md b/docs/stream-events.md index e323f5e99..24496089c 100644 --- a/docs/stream-events.md +++ b/docs/stream-events.md @@ -123,7 +123,7 @@ payload. Each event type is exported too (`TextDeltaEvent`, `ToolDoneEvent`, `RunDoneEvent`, ...), and `AgentEventOf<'tool.done'>` picks one by name. ```ts -import { isAgentEvent, isToolEvent, type AgentEvent } from 'lousho'; +import { isAgentEvent, isToolEvent, type AgentEvent } from '@linuxdevil/lousho'; function render(event: AgentEvent): string { switch (event.type) { diff --git a/docs/streaming.md b/docs/streaming.md index bdc23e4f0..3f9de9cbf 100644 --- a/docs/streaming.md +++ b/docs/streaming.md @@ -11,7 +11,7 @@ provider; with OpenRouter use `openrouter//` (e.g. > Snippets on this page leave some names to you: `provider` is any `LLMProvider` (offline: `createMockProvider()`); `agent` is a `createAgent()` result; `input` is the user's text, a `string`; `approvalStore` is an `ApprovalStore`; `toolRegistry` is a `ToolRegistry`. ```ts -import { createAgent } from 'lousho'; +import { createAgent } from '@linuxdevil/lousho'; const agent = createAgent({ model: 'openai/gpt-4o-mini' }); @@ -72,7 +72,7 @@ interface AgentRun extends AsyncIterable { throw synchronously from `stream()`. ```ts -import { createAgent } from 'lousho'; +import { createAgent } from '@linuxdevil/lousho'; const agent = createAgent({ model: 'openai/gpt-4o-mini' }); @@ -99,7 +99,7 @@ Output guardrails still check only the final reply of a `send()`, as without a listener. Sub-agents' events arrive tagged with `subagent`. ```ts -import { createAgent } from 'lousho'; +import { createAgent } from '@linuxdevil/lousho'; const agent = createAgent({ model: 'openai/gpt-4o-mini', @@ -150,7 +150,7 @@ save, so the transcript is complete when the loop ends. An aborted or failed run, or one you stop reading early, is not saved. ```ts -import { createAgent } from 'lousho'; +import { createAgent } from '@linuxdevil/lousho'; const agent = createAgent({ model: 'openai/gpt-4o-mini' }); const session = agent.session({ id: 'user-42' }); @@ -178,7 +178,7 @@ process, since everything is read from the approval store. A model chosen per run (`model` as a function) is the one the paused run used. ```ts -import { createAgent } from 'lousho'; +import { createAgent } from '@linuxdevil/lousho'; const agent = createAgent({ provider /* , a tool with needsApproval */ }); const paused = await agent.send('Delete the old reports.'); @@ -197,7 +197,7 @@ of `resumeAfterApproval()` (see [the executor API](./executor-api.md)) and returns the same `AgentRun`. ```ts -import { AgentExecutor, streamResumeAfterApproval } from 'lousho/executor'; +import { AgentExecutor, streamResumeAfterApproval } from '@linuxdevil/lousho/executor'; const paused = await AgentExecutor.execute({ agent, input, provider, toolRegistry, approvalStore }); const run = streamResumeAfterApproval({ id: paused.approvalId!, approved: true }, approvalStore, toolRegistry, provider); @@ -231,7 +231,7 @@ for approval is reported once, by the top-level `approval.requested` (which carries the sub-agent's call). ```ts -import { createAgent } from 'lousho'; +import { createAgent } from '@linuxdevil/lousho'; const researcher = createAgent({ provider, instructions: 'You research.', description: 'Finds sources' }); const lead = createAgent({ provider, instructions: 'You coordinate.', subagents: { researcher } }); @@ -272,7 +272,7 @@ step was running) and `run.done` (`'aborted'`), and `result` resolves with `finishReason: 'aborted'`. ```ts -import { createAgent } from 'lousho'; +import { createAgent } from '@linuxdevil/lousho'; const agent = createAgent({ model: 'openai/gpt-4o-mini' }); const run = agent.stream('Write a long story'); @@ -290,7 +290,7 @@ To add input to a run that is still going, or to redirect it, see [Queued input ## Example: terminal ```ts -import { createAgent, defineTool } from 'lousho'; +import { createAgent, defineTool } from '@linuxdevil/lousho'; import { z } from 'zod'; const getWeather = defineTool({ @@ -329,7 +329,7 @@ saved even when the client leaves (see [Next.js](./nextjs.md#routes)). ```ts import { createServer } from 'node:http'; -import { createAgent } from 'lousho'; +import { createAgent } from '@linuxdevil/lousho'; const agent = createAgent({ model: 'openai/gpt-4o-mini' }); @@ -372,7 +372,7 @@ with `{ sessionId, input }` streams `agent.session({ id }).stream(input)` as needs approval ends the stream with `approval.requested`: ```ts -import { AgentExecutor } from 'lousho/executor'; +import { AgentExecutor } from '@linuxdevil/lousho/executor'; const run = AgentExecutor.stream({ agent, input, provider, toolRegistry, approvalStore }); for await (const event of run) { diff --git a/docs/structured-output.md b/docs/structured-output.md index b4877ba02..2916dc857 100644 --- a/docs/structured-output.md +++ b/docs/structured-output.md @@ -8,7 +8,7 @@ the provider; with OpenRouter use `openrouter//` (e.g. ```ts import { z } from 'zod'; -import { createAgent } from 'lousho'; +import { createAgent } from '@linuxdevil/lousho'; const agent = createAgent({ model: 'openai/gpt-4o-mini', @@ -36,7 +36,7 @@ itself) or any other Standard Schema that can produce JSON Schema: ```ts import { z } from 'zod/v4'; -import { createAgent } from 'lousho'; +import { createAgent } from '@linuxdevil/lousho'; const agent = createAgent({ model: 'openai/gpt-4o-mini', output: z.object({ city: z.string() }) }); const { object } = await agent.send('Where is the Eiffel Tower?'); @@ -54,7 +54,7 @@ the tool take the data and keep `output` for a summary or status. ```ts import { z } from 'zod'; -import { createAgent } from 'lousho'; +import { createAgent } from '@linuxdevil/lousho'; declare function postComment(comment: { file: string; body: string }): Promise; @@ -77,7 +77,7 @@ from the data (a claim id), because a rerun of the whole flow extracts again: ```ts import { z } from 'zod'; -import { createAgent } from 'lousho'; +import { createAgent } from '@linuxdevil/lousho'; declare function askReviewer(claim: { claimId: string; amountCents: number }): Promise; declare const claims: { file(claim: { claimId: string; amountCents: number }, opts: { idempotencyKey: string }): Promise }; @@ -106,7 +106,7 @@ turn. There is no per-call `output` override: the schema is the agent's. ```ts import { z } from 'zod'; -import { createAgent } from 'lousho'; +import { createAgent } from '@linuxdevil/lousho'; const agent = createAgent({ model: 'openai/gpt-4o-mini', output: z.object({ city: z.string(), tempC: z.number() }) }); const session = agent.session(); @@ -136,7 +136,7 @@ read from the `object` of the remote stream's `run.done` event. ```ts import { z } from 'zod'; -import { createAgent } from 'lousho'; +import { createAgent } from '@linuxdevil/lousho'; const reporter = createAgent({ model: 'openai/gpt-4o-mini', @@ -161,7 +161,7 @@ and `result.object` has no key for it. A field that accepts `null` itself ```ts import { z } from 'zod'; -import { createAgent } from 'lousho'; +import { createAgent } from '@linuxdevil/lousho'; const agent = createAgent({ model: 'openrouter/openai/gpt-4o-mini', @@ -241,7 +241,7 @@ here; set `'final'` to keep it off those calls): ```ts import { z } from 'zod'; -import { createAgent } from 'lousho'; +import { createAgent } from '@linuxdevil/lousho'; const agent = createAgent({ model: 'openai/gpt-4o', @@ -278,7 +278,7 @@ where a failure is yours to report: ```ts import { z } from 'zod'; -import { createAgent } from 'lousho'; +import { createAgent } from '@linuxdevil/lousho'; const Invoice = z.object({ subtotal: z.number(), tax: z.number(), total: z.number() }); const agent = createAgent({ model: 'openai/gpt-4o-mini', output: Invoice }); @@ -330,7 +330,7 @@ progress, and `result.object` for the answer. The UI bindings keep it as `partialObject`. ```ts -import { createAgent } from 'lousho'; +import { createAgent } from '@linuxdevil/lousho'; import { z } from 'zod'; const planner = createAgent({ model: 'openai/gpt-4o-mini', output: z.object({ days: z.array(z.string()) }) }); @@ -347,8 +347,8 @@ schema, or use `createAgent()` for the inferred type. ```ts import { z } from 'zod'; -import { AgentBuilder, AgentExecutor } from 'lousho/executor'; -import { createMockProvider } from 'lousho'; +import { AgentBuilder, AgentExecutor } from '@linuxdevil/lousho/executor'; +import { createMockProvider } from '@linuxdevil/lousho'; const Ticket = z.object({ title: z.string(), priority: z.enum(['low', 'high']) }); const agent = AgentBuilder.create().setName('triage').setPrompt('You triage bug reports.').build(); @@ -362,8 +362,8 @@ With `mockModel`, script the JSON text the model would write: ```ts import { z } from 'zod'; -import { createAgent } from 'lousho'; -import { mockModel } from 'lousho/testing'; +import { createAgent } from '@linuxdevil/lousho'; +import { mockModel } from '@linuxdevil/lousho/testing'; const agent = createAgent({ provider: mockModel(['{"city":"Paris","tempC":21}']), output: z.object({ city: z.string(), tempC: z.number() }) }); const { object } = await agent.send('Weather in Paris?'); diff --git a/docs/sub-agents.md b/docs/sub-agents.md index 936b7d476..feda9a74a 100644 --- a/docs/sub-agents.md +++ b/docs/sub-agents.md @@ -21,7 +21,7 @@ Give each sub-agent a `description` (the lead model reads it to choose), then pass them to the lead as `subagents`: ```ts -import { createAgent } from 'lousho'; +import { createAgent } from '@linuxdevil/lousho'; const researcher = createAgent({ provider, @@ -109,8 +109,8 @@ needs no code. A lead that keeps its work across turns or restarts needs a store and a session: ```ts -import { createAgent } from 'lousho'; -import { SqliteStore } from 'lousho/sqlite'; +import { createAgent } from '@linuxdevil/lousho'; +import { SqliteStore } from '@linuxdevil/lousho/sqlite'; const store = new SqliteStore('./.lousho/agent.db'); const researcher = createAgent({ provider, instructions: 'You research.', description: 'Finds and summarizes sources' }); @@ -158,7 +158,7 @@ sometimes pass one sub-agent's `taskId` to another and waste a turn on the error; this removes the option. ```ts -import { createAgent } from 'lousho'; +import { createAgent } from '@linuxdevil/lousho'; const analyst = createAgent({ provider, instructions: 'You analyze.', description: 'Analyzes data' }); const lead = createAgent({ provider, subagents: { analyst }, subagentOptions: { continuation: false } }); @@ -194,7 +194,7 @@ At most `maxConcurrent` background sub-agents of one lead run run at once as slots free. Set it with `subagentOptions` on `createAgent()`: ```ts -import { createAgent } from 'lousho'; +import { createAgent } from '@linuxdevil/lousho'; const researcher = createAgent({ provider, instructions: 'You research.', description: 'Finds and summarizes sources' }); @@ -235,7 +235,7 @@ events arrive before the lead's `run.done`. The result lists every background task of the run with its final status: ```ts -import { createAgent } from 'lousho'; +import { createAgent } from '@linuxdevil/lousho'; const researcher = createAgent({ provider, instructions: 'You research.', description: 'Finds and summarizes sources' }); const lead = createAgent({ provider, instructions: 'You coordinate research.', subagents: { researcher } }); @@ -287,7 +287,7 @@ Limits: cannot be resumed: the model chose not to wait for it. ```ts -import { createAgent, defineTool } from 'lousho'; +import { createAgent, defineTool } from '@linuxdevil/lousho'; import { z } from 'zod'; const shell = defineTool({ @@ -318,7 +318,7 @@ the set can change between runs; `resolve()` is called when the lead picks a name (return `undefined` for an unknown one). ```ts -import { createAgent, type SubagentCatalog } from 'lousho'; +import { createAgent, type SubagentCatalog } from '@linuxdevil/lousho'; const experts = new Map([ ['researcher', createAgent({ provider, instructions: 'You research.' })], @@ -340,7 +340,7 @@ next to local ones, and the lead delegates to it with the same `task` tool, including `background: true`. ```ts -import { createAgent, remoteAgent } from 'lousho'; +import { createAgent, remoteAgent } from '@linuxdevil/lousho'; const lead = createAgent({ provider, @@ -404,7 +404,7 @@ final answer becomes the `task` result; a continuation that pauses again pauses the lead again. ```ts -import type { SimpleAgent } from 'lousho'; +import type { SimpleAgent } from '@linuxdevil/lousho'; declare const lead: SimpleAgent; // the lead agent above const paused = await lead.send('Deploy the docs site'); @@ -438,7 +438,7 @@ use the ready-made Pi adapter. Pi's tools (`read`, `bash`, `edit`, `write`, ...) work on a local directory. ```ts -import { createAgent, piAgent, ask, allow } from 'lousho'; +import { createAgent, piAgent, ask, allow } from '@linuxdevil/lousho'; import { ModelRuntime } from '@earendil-works/pi-coding-agent'; const runtime = await ModelRuntime.create(); // reads ~/.pi/agent auth, env keys @@ -529,7 +529,7 @@ the enclosing sub-agent as `parent` when nested deeper. A hook that should only see the top-level run returns early: ```ts -import { HookRegistry } from 'lousho'; +import { HookRegistry } from '@linuxdevil/lousho'; const hooks = new HookRegistry(); hooks.register({ @@ -565,7 +565,7 @@ sub-agents it runs inside (e.g. `['researcher']`). Approve or reject it with is needed): ```ts -import { createAgent } from 'lousho'; +import { createAgent } from '@linuxdevil/lousho'; const lead = createAgent({ provider, @@ -622,8 +622,8 @@ Resume with `resumeAfterApproval()`, passing the same `subagents` option as the paused run: ```ts -import { AgentExecutor, resumeAfterApproval, ToolRegistry } from 'lousho/executor'; -import { createAgent } from 'lousho'; +import { AgentExecutor, resumeAfterApproval, ToolRegistry } from '@linuxdevil/lousho/executor'; +import { createAgent } from '@linuxdevil/lousho'; const subagents = { researcher: createAgent({ provider, instructions: 'You research.', description: 'Researches' }) }; diff --git a/docs/svelte.md b/docs/svelte.md index 641613adc..002369681 100644 --- a/docs/svelte.md +++ b/docs/svelte.md @@ -6,13 +6,13 @@ sources, the same state and the same actions, over the same field means, the two kinds of source, how approvals work and the server side; this page covers what differs. -It lives in the `lousho/svelte` subpath. The store implements +It lives in the `@linuxdevil/lousho/svelte` subpath. The store implements Svelte's store contract (`subscribe(run) => unsubscribe`) by hand, so it works with `$agent` in Svelte 4 and 5 and needs no `svelte` import: there is no peer dependency to install. ```ts -import { loushoAgent } from 'lousho/svelte'; +import { loushoAgent } from '@linuxdevil/lousho/svelte'; // In a component's