wizards 0.1.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +110 -0
- package/README.md +170 -0
- package/apps/runtime/dist/agents/architect.js +453 -0
- package/apps/runtime/dist/agents/project-assistant.js +354 -0
- package/apps/runtime/dist/agents/subscription.js +206 -0
- package/apps/runtime/dist/agents/thought.js +21 -0
- package/apps/runtime/dist/agents/website.js +197 -0
- package/apps/runtime/dist/app.js +153 -0
- package/apps/runtime/dist/auth/account.js +121 -0
- package/apps/runtime/dist/auth/index.js +329 -0
- package/apps/runtime/dist/auth/keys.js +55 -0
- package/apps/runtime/dist/auth/local-ticket.js +23 -0
- package/apps/runtime/dist/authoring/example.js +73 -0
- package/apps/runtime/dist/authoring/guide.js +259 -0
- package/apps/runtime/dist/authoring/ops.js +117 -0
- package/apps/runtime/dist/cli/autostart.js +134 -0
- package/apps/runtime/dist/cli/clients.js +44 -0
- package/apps/runtime/dist/cli/environment.js +81 -0
- package/apps/runtime/dist/cli/exec.js +45 -0
- package/apps/runtime/dist/cli/home.js +74 -0
- package/apps/runtime/dist/cli/machine.js +145 -0
- package/apps/runtime/dist/cli/main.js +125 -0
- package/apps/runtime/dist/cli/setup.js +273 -0
- package/apps/runtime/dist/cli/start.js +190 -0
- package/apps/runtime/dist/cli/status.js +120 -0
- package/apps/runtime/dist/cli/ui.js +12 -0
- package/apps/runtime/dist/cli/update.js +58 -0
- package/apps/runtime/dist/connectors/builtin.js +70 -0
- package/apps/runtime/dist/connectors/external.js +400 -0
- package/apps/runtime/dist/connectors/gmail.js +107 -0
- package/apps/runtime/dist/connectors/imap.js +217 -0
- package/apps/runtime/dist/connectors/index.js +266 -0
- package/apps/runtime/dist/connectors/mail.js +72 -0
- package/apps/runtime/dist/connectors/outlook.js +90 -0
- package/apps/runtime/dist/credits/credits.js +103 -0
- package/apps/runtime/dist/credits/estimate.js +132 -0
- package/apps/runtime/dist/db/adopt.js +149 -0
- package/apps/runtime/dist/db/client.js +146 -0
- package/apps/runtime/dist/db/control-schema.js +70 -0
- package/apps/runtime/dist/db/migrations/control/0000_last_absorbing_man.sql +47 -0
- package/apps/runtime/dist/db/migrations/control/0001_gorgeous_nicolaos.sql +41 -0
- package/apps/runtime/dist/db/migrations/control/0002_secret_penance.sql +1 -0
- package/apps/runtime/dist/db/migrations/control/0003_nasty_mordo.sql +3 -0
- package/apps/runtime/dist/db/migrations/control/0004_breezy_eddie_brock.sql +6 -0
- package/apps/runtime/dist/db/migrations/control/meta/0000_snapshot.json +311 -0
- package/apps/runtime/dist/db/migrations/control/meta/0001_snapshot.json +599 -0
- package/apps/runtime/dist/db/migrations/control/meta/0002_snapshot.json +607 -0
- package/apps/runtime/dist/db/migrations/control/meta/0003_snapshot.json +311 -0
- package/apps/runtime/dist/db/migrations/control/meta/0004_snapshot.json +350 -0
- package/apps/runtime/dist/db/migrations/control/meta/_journal.json +41 -0
- package/apps/runtime/dist/db/migrations/tenant/0000_eminent_freak.sql +188 -0
- package/apps/runtime/dist/db/migrations/tenant/0001_salty_master_chief.sql +1 -0
- package/apps/runtime/dist/db/migrations/tenant/0002_cloudy_firebird.sql +1 -0
- package/apps/runtime/dist/db/migrations/tenant/0003_brown_warbound.sql +52 -0
- package/apps/runtime/dist/db/migrations/tenant/0004_romantic_nocturne.sql +6 -0
- package/apps/runtime/dist/db/migrations/tenant/meta/0000_snapshot.json +1323 -0
- package/apps/runtime/dist/db/migrations/tenant/meta/0001_snapshot.json +1330 -0
- package/apps/runtime/dist/db/migrations/tenant/meta/0002_snapshot.json +1337 -0
- package/apps/runtime/dist/db/migrations/tenant/meta/0003_snapshot.json +1605 -0
- package/apps/runtime/dist/db/migrations/tenant/meta/0004_snapshot.json +1645 -0
- package/apps/runtime/dist/db/migrations/tenant/meta/_journal.json +41 -0
- package/apps/runtime/dist/db/schema.js +278 -0
- package/apps/runtime/dist/documents/parse.js +230 -0
- package/apps/runtime/dist/engenty/connections-external/build-connector.js +193 -0
- package/apps/runtime/dist/engenty/connections-external/errors.js +11 -0
- package/apps/runtime/dist/engenty/connections-external/import-service.js +270 -0
- package/apps/runtime/dist/engenty/connections-external/importer/classify.js +34 -0
- package/apps/runtime/dist/engenty/connections-external/importer/map-auth.js +185 -0
- package/apps/runtime/dist/engenty/connections-external/importer/normalize-mcp.js +63 -0
- package/apps/runtime/dist/engenty/connections-external/importer/normalize-openapi.js +130 -0
- package/apps/runtime/dist/engenty/connections-external/invoke/http-invoker.js +143 -0
- package/apps/runtime/dist/engenty/connections-external/invoke/mcp-client.js +143 -0
- package/apps/runtime/dist/engenty/connections-external/net/guarded-fetch.js +119 -0
- package/apps/runtime/dist/engenty/connections-external/oauth-dcr.js +88 -0
- package/apps/runtime/dist/engenty/connections-external/registry-client.js +336 -0
- package/apps/runtime/dist/engenty/connections-external/registry-source.js +82 -0
- package/apps/runtime/dist/engenty/connections-external/types.js +7 -0
- package/apps/runtime/dist/engenty/connections-github/connector.js +209 -0
- package/apps/runtime/dist/engenty/connections-google/connectors/calendar.js +359 -0
- package/apps/runtime/dist/engenty/connections-google/connectors/contacts.js +94 -0
- package/apps/runtime/dist/engenty/connections-google/connectors/drive.js +290 -0
- package/apps/runtime/dist/engenty/connections-google/connectors/gmail.js +567 -0
- package/apps/runtime/dist/engenty/connections-google/definitions.js +11 -0
- package/apps/runtime/dist/engenty/connections-google/shared.js +73 -0
- package/apps/runtime/dist/engenty/connections-hubspot/connector.js +140 -0
- package/apps/runtime/dist/engenty/connections-microsoft/action.js +17 -0
- package/apps/runtime/dist/engenty/connections-microsoft/graph.js +114 -0
- package/apps/runtime/dist/engenty/connections-microsoft/onedrive.js +286 -0
- package/apps/runtime/dist/engenty/connections-microsoft/outlook.js +464 -0
- package/apps/runtime/dist/engenty/connections-s3/s3.js +328 -0
- package/apps/runtime/dist/engenty/connections-sdk/files-capability.js +86 -0
- package/apps/runtime/dist/engenty/connections-sdk/oauth2.js +150 -0
- package/apps/runtime/dist/engenty/connections-sdk/registry.js +89 -0
- package/apps/runtime/dist/engenty/connections-sdk/storage-capability.js +84 -0
- package/apps/runtime/dist/engenty/connections-sdk/types.js +30 -0
- package/apps/runtime/dist/engenty/connections-slack/connector.js +360 -0
- package/apps/runtime/dist/engenty/csv-import/csv-matrix.js +178 -0
- package/apps/runtime/dist/engenty/csv-import/import-sources.js +87 -0
- package/apps/runtime/dist/engenty/csv-import/parse-csv.js +129 -0
- package/apps/runtime/dist/engenty/csv-import/serialize-csv.js +20 -0
- package/apps/runtime/dist/engenty/csv-import/types.js +1 -0
- package/apps/runtime/dist/engenty/csv-import/xlsx-workbook.js +159 -0
- package/apps/runtime/dist/engenty/doc-converter/interface.js +9 -0
- package/apps/runtime/dist/engenty/doc-converter/page-break.js +92 -0
- package/apps/runtime/dist/engenty/doc-converter/providers/local/index.js +72 -0
- package/apps/runtime/dist/engenty/document-scanner/schemas/classifier.js +30 -0
- package/apps/runtime/dist/engenty/document-scanner/schemas/index.js +4 -0
- package/apps/runtime/dist/engenty/document-scanner/schemas/invoice.js +73 -0
- package/apps/runtime/dist/engenty/document-scanner/schemas/receipt.js +63 -0
- package/apps/runtime/dist/engenty/document-scanner/schemas/shared.js +40 -0
- package/apps/runtime/dist/engenty/search-index/chunks.js +164 -0
- package/apps/runtime/dist/engenty/search-index/contracts.js +9 -0
- package/apps/runtime/dist/engenty/shims/connections-sdk.js +39 -0
- package/apps/runtime/dist/engenty/shims/plugin-sdk.js +1 -0
- package/apps/runtime/dist/engenty/shims/web-ingest.js +3 -0
- package/apps/runtime/dist/engenty/web-ingest/ssrf.js +277 -0
- package/apps/runtime/dist/engine/asks.js +60 -0
- package/apps/runtime/dist/engine/events.js +46 -0
- package/apps/runtime/dist/engine/input.js +170 -0
- package/apps/runtime/dist/engine/prepare.js +51 -0
- package/apps/runtime/dist/engine/requirements.js +124 -0
- package/apps/runtime/dist/engine/resources.js +89 -0
- package/apps/runtime/dist/engine/runner.js +560 -0
- package/apps/runtime/dist/engine/steps.js +598 -0
- package/apps/runtime/dist/engine/template.js +183 -0
- package/apps/runtime/dist/engine/types.js +3 -0
- package/apps/runtime/dist/env.js +224 -0
- package/apps/runtime/dist/files/blobs.js +22 -0
- package/apps/runtime/dist/files/objects.js +86 -0
- package/apps/runtime/dist/files/storage.js +95 -0
- package/apps/runtime/dist/geocode.js +58 -0
- package/apps/runtime/dist/harness/claude.js +138 -0
- package/apps/runtime/dist/harness/codex.js +228 -0
- package/apps/runtime/dist/harness/cursor.js +93 -0
- package/apps/runtime/dist/harness/env.js +124 -0
- package/apps/runtime/dist/harness/gemini.js +126 -0
- package/apps/runtime/dist/harness/index.js +99 -0
- package/apps/runtime/dist/harness/model.js +267 -0
- package/apps/runtime/dist/harness/prompt.js +121 -0
- package/apps/runtime/dist/harness/terminal.js +193 -0
- package/apps/runtime/dist/harness/types.js +7 -0
- package/apps/runtime/dist/index.js +76 -0
- package/apps/runtime/dist/limits.js +57 -0
- package/apps/runtime/dist/link-preview.js +59 -0
- package/apps/runtime/dist/manage.js +151 -0
- package/apps/runtime/dist/mcp/auth.js +102 -0
- package/apps/runtime/dist/mcp/bridge.js +59 -0
- package/apps/runtime/dist/mcp/handler.js +42 -0
- package/apps/runtime/dist/mcp/scopes.js +5 -0
- package/apps/runtime/dist/mcp/tools.js +401 -0
- package/apps/runtime/dist/media/ffmpeg.js +121 -0
- package/apps/runtime/dist/media/generate.js +120 -0
- package/apps/runtime/dist/media/marking.js +121 -0
- package/apps/runtime/dist/media/transcribe.js +30 -0
- package/apps/runtime/dist/model-errors.js +3 -0
- package/apps/runtime/dist/models.js +489 -0
- package/apps/runtime/dist/plugin.js +61 -0
- package/apps/runtime/dist/render/chromium.js +85 -0
- package/apps/runtime/dist/render/convert.js +161 -0
- package/apps/runtime/dist/render/downloads.js +277 -0
- package/apps/runtime/dist/render/guard.js +25 -0
- package/apps/runtime/dist/routes/api.js +74 -0
- package/apps/runtime/dist/routes/delivery.js +52 -0
- package/apps/runtime/dist/routes/internal.js +49 -0
- package/apps/runtime/dist/routes/marketplace.js +36 -0
- package/apps/runtime/dist/routes/runs.js +805 -0
- package/apps/runtime/dist/routes/studio.js +702 -0
- package/apps/runtime/dist/routes/wizard-stream.js +30 -0
- package/apps/runtime/dist/running.js +39 -0
- package/apps/runtime/dist/sandbox/agentos.js +191 -0
- package/apps/runtime/dist/sandbox/docker.js +77 -0
- package/apps/runtime/dist/sandbox/index.js +30 -0
- package/apps/runtime/dist/sandbox/types.js +1 -0
- package/apps/runtime/dist/secrets/crypto.js +28 -0
- package/apps/runtime/dist/secrets/signing.js +34 -0
- package/apps/runtime/dist/secrets/vault.js +59 -0
- package/apps/runtime/dist/services/architect.js +92 -0
- package/apps/runtime/dist/services/brand.js +20 -0
- package/apps/runtime/dist/services/cloud.js +52 -0
- package/apps/runtime/dist/services/draft-events.js +27 -0
- package/apps/runtime/dist/services/errors.js +24 -0
- package/apps/runtime/dist/services/files.js +129 -0
- package/apps/runtime/dist/services/links.js +16 -0
- package/apps/runtime/dist/services/marketplace.js +292 -0
- package/apps/runtime/dist/services/package.js +202 -0
- package/apps/runtime/dist/services/profile.js +23 -0
- package/apps/runtime/dist/services/project-files.js +266 -0
- package/apps/runtime/dist/services/project-index.js +109 -0
- package/apps/runtime/dist/services/projects.js +181 -0
- package/apps/runtime/dist/services/runs.js +224 -0
- package/apps/runtime/dist/services/shares.js +129 -0
- package/apps/runtime/dist/services/widgets.js +34 -0
- package/apps/runtime/dist/services/wizards.js +342 -0
- package/apps/runtime/dist/settings.js +16 -0
- package/apps/runtime/dist/store/index.js +279 -0
- package/apps/runtime/dist/tenants/control.js +106 -0
- package/apps/runtime/dist/tenants/tenant.js +26 -0
- package/apps/runtime/dist/tools/browser.js +380 -0
- package/apps/runtime/dist/tools/connector.js +204 -0
- package/apps/runtime/dist/tools/index.js +234 -0
- package/apps/runtime/dist/tools/mail.js +104 -0
- package/apps/runtime/dist/tools/net-guard.js +59 -0
- package/apps/runtime/dist/tools/project.js +53 -0
- package/apps/runtime/dist/tools/shared.js +42 -0
- package/apps/runtime/dist/tools/store.js +238 -0
- package/apps/runtime/dist/update.js +118 -0
- package/apps/runtime/dist/widgets/bundle.js +99 -0
- package/apps/runtime/dist/widgets/render.js +312 -0
- package/apps/runtime/dist/widgets/runtime.js +84 -0
- package/apps/runtime/dist/widgets/script.js +30 -0
- package/apps/runtime/dist/widgets/step.js +192 -0
- package/apps/web/dist/ai-labels/ai-black.svg +20 -0
- package/apps/web/dist/ai-labels/ai-generated-black.svg +31 -0
- package/apps/web/dist/ai-labels/ai-generated-white.svg +32 -0
- package/apps/web/dist/ai-labels/ai-modified-black.svg +30 -0
- package/apps/web/dist/ai-labels/ai-modified-white.svg +31 -0
- package/apps/web/dist/ai-labels/ai-white.svg +21 -0
- package/apps/web/dist/assets/ChangelogDialog-O00vEnbm.js +1 -0
- package/apps/web/dist/assets/ChatPanel-CH9ry0Hp.js +1 -0
- package/apps/web/dist/assets/CreditsDialog-DRzecwUw.js +1 -0
- package/apps/web/dist/assets/EditorPage-BRnBf02Q.js +2 -0
- package/apps/web/dist/assets/FlowPreview-DDOj8IAH.js +1 -0
- package/apps/web/dist/assets/HomePage-CAGTTG3N.js +1 -0
- package/apps/web/dist/assets/ImportWizard-0RRrAnX3.js +1 -0
- package/apps/web/dist/assets/LocalRuntime-BrP-ENHg.css +1 -0
- package/apps/web/dist/assets/LocalRuntime-C-TBYrzs.js +16 -0
- package/apps/web/dist/assets/NewWizardPage-kI90h3_9.js +2 -0
- package/apps/web/dist/assets/PublicRunner-CKRDeIVd.js +1 -0
- package/apps/web/dist/assets/RunnerView-Rc4H8wYH.js +2 -0
- package/apps/web/dist/assets/SettingsPage-CESMuZxq.js +2 -0
- package/apps/web/dist/assets/SetupPage-D1rrYU14.js +1 -0
- package/apps/web/dist/assets/SharePage-COTAmOgh.js +1 -0
- package/apps/web/dist/assets/api-BY28cjGY.js +10 -0
- package/apps/web/dist/assets/arrow-left-UpjOBfQp.js +1 -0
- package/apps/web/dist/assets/arrow-right-CucQycEy.js +1 -0
- package/apps/web/dist/assets/changelog-p3wsaDDS.js +1 -0
- package/apps/web/dist/assets/check-Bjx41xbB.js +1 -0
- package/apps/web/dist/assets/createLucideIcon-DBu2Ovxj.js +1 -0
- package/apps/web/dist/assets/definition-cy7uvY5L.js +55 -0
- package/apps/web/dist/assets/engenty-CWIFpgKq.js +1 -0
- package/apps/web/dist/assets/esm-D1OEhjTK.js +7 -0
- package/apps/web/dist/assets/external-link-CRg6DVdO.js +1 -0
- package/apps/web/dist/assets/fluffy-engenty-CJ3wCQgT.js +456 -0
- package/apps/web/dist/assets/index-CQ_q9iMO.css +1 -0
- package/apps/web/dist/assets/index-D0gxh90t.js +10 -0
- package/apps/web/dist/assets/jsx-runtime-Dk72oS4N.js +1 -0
- package/apps/web/dist/assets/meta-BbSdtTUa.js +1 -0
- package/apps/web/dist/assets/mic-CeqQ89Dj.js +1 -0
- package/apps/web/dist/assets/oss-credits-cPuZUFHD.js +1 -0
- package/apps/web/dist/assets/outputs-D6pxH6ve.js +69 -0
- package/apps/web/dist/assets/plus-HA210wXA.js +1 -0
- package/apps/web/dist/assets/preload-helper-CNEI7N0N.js +1 -0
- package/apps/web/dist/assets/qr-scanner-worker.min-CJaXo3yu.js +98 -0
- package/apps/web/dist/assets/qr-scanner.min-C_UI0gH-.js +1 -0
- package/apps/web/dist/assets/type-BzYgVWKL.js +1 -0
- package/apps/web/dist/assets/ui-Ddwl35A6.js +1 -0
- package/apps/web/dist/assets/upload-DsgLfQ3U.js +1 -0
- package/apps/web/dist/assets/video-Dk1fuTDl.js +1 -0
- package/apps/web/dist/assets/wand-sparkles-Bf97fs8T.js +1 -0
- package/apps/web/dist/embed.js +102 -0
- package/apps/web/dist/favicon.svg +1 -0
- package/apps/web/dist/icons/apple-touch-icon.png +0 -0
- package/apps/web/dist/icons/icon-192.png +0 -0
- package/apps/web/dist/icons/icon-512.png +0 -0
- package/apps/web/dist/icons/icon-maskable-512.png +0 -0
- package/apps/web/dist/index.html +46 -0
- package/apps/web/dist/install.sh +532 -0
- package/apps/web/dist/manifest.webmanifest +20 -0
- package/apps/web/dist/sw.js +15 -0
- package/bin/wizards.mjs +13 -0
- package/node_modules/@engenty-wizards/shared/dist/connectors.d.ts +77 -0
- package/node_modules/@engenty-wizards/shared/dist/connectors.js +2 -0
- package/node_modules/@engenty-wizards/shared/dist/definition.d.ts +1415 -0
- package/node_modules/@engenty-wizards/shared/dist/definition.js +762 -0
- package/node_modules/@engenty-wizards/shared/dist/engenty/data-tables/columns-format.d.ts +5 -0
- package/node_modules/@engenty-wizards/shared/dist/engenty/data-tables/columns-format.js +132 -0
- package/node_modules/@engenty-wizards/shared/dist/engenty/data-tables/columns-value.d.ts +11 -0
- package/node_modules/@engenty-wizards/shared/dist/engenty/data-tables/columns-value.js +209 -0
- package/node_modules/@engenty-wizards/shared/dist/engenty/data-tables/columns.d.ts +532 -0
- package/node_modules/@engenty-wizards/shared/dist/engenty/data-tables/columns.js +216 -0
- package/node_modules/@engenty-wizards/shared/dist/engenty/data-tables/index.d.ts +3 -0
- package/node_modules/@engenty-wizards/shared/dist/engenty/data-tables/index.js +3 -0
- package/node_modules/@engenty-wizards/shared/dist/engenty/search-index/catalog-lexical.d.ts +14 -0
- package/node_modules/@engenty-wizards/shared/dist/engenty/search-index/catalog-lexical.js +166 -0
- package/node_modules/@engenty-wizards/shared/dist/engenty/search-index/catalog-rank.d.ts +33 -0
- package/node_modules/@engenty-wizards/shared/dist/engenty/search-index/catalog-rank.js +99 -0
- package/node_modules/@engenty-wizards/shared/dist/marketplace-entry.d.ts +35 -0
- package/node_modules/@engenty-wizards/shared/dist/marketplace-entry.js +86 -0
- package/node_modules/@engenty-wizards/shared/dist/marketplace-search.d.ts +20 -0
- package/node_modules/@engenty-wizards/shared/dist/marketplace-search.js +133 -0
- package/node_modules/@engenty-wizards/shared/dist/marketplace.d.ts +298 -0
- package/node_modules/@engenty-wizards/shared/dist/marketplace.js +211 -0
- package/node_modules/@engenty-wizards/shared/dist/merge.d.ts +7 -0
- package/node_modules/@engenty-wizards/shared/dist/merge.js +40 -0
- package/node_modules/@engenty-wizards/shared/dist/projects.d.ts +74 -0
- package/node_modules/@engenty-wizards/shared/dist/projects.js +96 -0
- package/node_modules/@engenty-wizards/shared/dist/run.d.ts +219 -0
- package/node_modules/@engenty-wizards/shared/dist/run.js +1 -0
- package/node_modules/@engenty-wizards/shared/dist/store.d.ts +100 -0
- package/node_modules/@engenty-wizards/shared/dist/store.js +44 -0
- package/node_modules/@engenty-wizards/shared/dist/workspace.d.ts +16 -0
- package/node_modules/@engenty-wizards/shared/dist/workspace.js +51 -0
- package/node_modules/@engenty-wizards/shared/package.json +10 -0
- package/package.json +62 -0
- package/plugin/.claude-plugin/plugin.json +9 -0
- package/plugin/.mcp.json +8 -0
- package/plugin/commands/wizard.md +8 -0
- package/plugin/skills/build-wizard/SKILL.md +27 -0
|
@@ -0,0 +1,354 @@
|
|
|
1
|
+
import { anthropic } from "@ai-sdk/anthropic";
|
|
2
|
+
import { FACT_TYPES, factKey, PROJECT_FILE_KINDS, PROJECT_LIMITS, projectFileLimit, } from "@engenty-wizards/shared/projects";
|
|
3
|
+
import { Agent } from "@mastra/core/agent";
|
|
4
|
+
import { createTool } from "@mastra/core/tools";
|
|
5
|
+
import { z } from "zod";
|
|
6
|
+
import { attachTools, costOf, gatewayTools, textModel } from "../models.js";
|
|
7
|
+
import { ServiceError } from "../services/errors.js";
|
|
8
|
+
import { addProjectFile, projectFileContent, projectFiles, updateProjectFile, } from "../services/project-files.js";
|
|
9
|
+
import { documentText } from "../services/project-index.js";
|
|
10
|
+
import { brandColorSchema, ownedProject, updateProject } from "../services/projects.js";
|
|
11
|
+
import { safeFetch } from "../tools/net-guard.js";
|
|
12
|
+
import { readWebsite } from "./website.js";
|
|
13
|
+
const MAX_STEPS = 24;
|
|
14
|
+
const SYSTEM = `You help the admin of "engenty wizards" set up a PROJECT: what every wizard of the project draws on — who they are (title, about), the brand (logos, colours), assets (images, graphics, videos), documents, and facts (label → value: address, VAT id, phone, opening hours — anything).
|
|
15
|
+
|
|
16
|
+
You talk to the admin. Answer in the admin's language, briefly and warmly. Never mention JSON, ids, keys or tool names.
|
|
17
|
+
|
|
18
|
+
HOW YOU WORK
|
|
19
|
+
- The project as it is now comes with every message. You change it ONLY through tools.
|
|
20
|
+
- A website: read_website it first. You get the page's text, colours from its stylesheet, logo candidates and the links to imprint / contact / about. Read those pages too (at most four more) for address, VAT id, register number, phone, email, bank details, who runs it.
|
|
21
|
+
- Facts: one fact per thing, a short label in the admin's language ("Adresse", "UID", "Telefon"), the value on one line where it fits. Only what a source or the admin states — never guess, never invent. Keep the facts that are there; a fact with a known label is updated.
|
|
22
|
+
- About: two to five sentences — what they do, for whom, what sets them apart, the tone they write in. Rewrite it only when you learned something new or the admin asks.
|
|
23
|
+
- Colours: two to six, the main one first, each with a name that says what it is for ("Primär", "Hintergrund", "Text"). Take them from the stylesheet's variables or its most used colours, never from photos. set_colors replaces the list: pass all colours that should remain.
|
|
24
|
+
- Logos: save the best candidate (an SVG or a large PNG rather than a favicon); a second one only if it truly differs (for dark ground, a signet). A few images that show the business (header, team, products) may be saved as assets — a handful, never everything.
|
|
25
|
+
- Files the admin attached are stored already; the message names them. look_at_file shows you a picture or a document's text. Give a file a description with describe_file when it has none or the admin says what it is. Take facts from a document when the admin asks for it.
|
|
26
|
+
- Call independent tools together. Work in few steps.
|
|
27
|
+
- Finish with one to three sentences: what you filled in, what is still missing, and at most one question.`;
|
|
28
|
+
function activity(tool, args) {
|
|
29
|
+
const host = (u) => {
|
|
30
|
+
try {
|
|
31
|
+
return new URL(/^https?:/i.test(String(u)) ? String(u) : `https://${u}`).hostname;
|
|
32
|
+
}
|
|
33
|
+
catch {
|
|
34
|
+
return "";
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
switch (tool) {
|
|
38
|
+
case "read_website":
|
|
39
|
+
return args.url ? `Liest ${host(args.url)} …` : "Liest die Website …";
|
|
40
|
+
case "save_file":
|
|
41
|
+
return args.kind === "logo" ? "Holt das Logo …" : "Holt eine Datei …";
|
|
42
|
+
case "set_profile":
|
|
43
|
+
return "Schreibt Titel und Beschreibung …";
|
|
44
|
+
case "set_facts":
|
|
45
|
+
case "remove_facts":
|
|
46
|
+
return "Trägt Fakten ein …";
|
|
47
|
+
case "set_colors":
|
|
48
|
+
return "Trägt die Farben ein …";
|
|
49
|
+
case "look_at_file":
|
|
50
|
+
return "Sieht sich eine Datei an …";
|
|
51
|
+
case "describe_file":
|
|
52
|
+
return "Beschreibt eine Datei …";
|
|
53
|
+
default:
|
|
54
|
+
return null;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
async function attempt(run) {
|
|
58
|
+
try {
|
|
59
|
+
return await run();
|
|
60
|
+
}
|
|
61
|
+
catch (err) {
|
|
62
|
+
return { error: err instanceof ServiceError ? err.message : err.message };
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
async function projectState(userId, projectId) {
|
|
66
|
+
const p = await ownedProject(userId, projectId);
|
|
67
|
+
const files = await projectFiles(projectId);
|
|
68
|
+
return {
|
|
69
|
+
title: p.brand.name ?? "",
|
|
70
|
+
about: p.brand.about ?? "",
|
|
71
|
+
colors: p.brand.colors ?? [],
|
|
72
|
+
facts: p.facts.map(({ label, value }) => ({ label, value })),
|
|
73
|
+
files: files.map((f) => ({
|
|
74
|
+
id: f.id,
|
|
75
|
+
kind: f.kind,
|
|
76
|
+
name: f.name,
|
|
77
|
+
description: f.description,
|
|
78
|
+
...(f.status === "ready" ? {} : { status: f.status }),
|
|
79
|
+
})),
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
function buildTools(input, resolved, wrote) {
|
|
83
|
+
const { userId, projectId, signal } = input;
|
|
84
|
+
// Said from inside the tool: an installed AI client runs tools without the stream showing it.
|
|
85
|
+
const doing = (tool, args = {}) => {
|
|
86
|
+
const label = activity(tool, args);
|
|
87
|
+
if (label) {
|
|
88
|
+
input.onActivity?.(label);
|
|
89
|
+
}
|
|
90
|
+
};
|
|
91
|
+
// Tools of one step run together; writes to the project's row take turns.
|
|
92
|
+
let turn = Promise.resolve();
|
|
93
|
+
const inTurn = (run) => {
|
|
94
|
+
const next = turn.then(run, run);
|
|
95
|
+
turn = next.catch(() => undefined);
|
|
96
|
+
return next;
|
|
97
|
+
};
|
|
98
|
+
const tools = {
|
|
99
|
+
read_website: createTool({
|
|
100
|
+
id: "read_website",
|
|
101
|
+
description: "Read a public web page: its text as Markdown, the colours of its stylesheet (named variables and the most used ones), logo candidates and images with their addresses, and the links to imprint, contact and about pages.",
|
|
102
|
+
inputSchema: z.object({ url: z.string().describe("The page's address") }),
|
|
103
|
+
execute: ({ url }) => {
|
|
104
|
+
doing("read_website", { url });
|
|
105
|
+
return attempt(() => readWebsite(url, signal));
|
|
106
|
+
},
|
|
107
|
+
}),
|
|
108
|
+
set_profile: createTool({
|
|
109
|
+
id: "set_profile",
|
|
110
|
+
description: "Set the project's title (the company, brand or undertaking) and/or its about text.",
|
|
111
|
+
inputSchema: z.object({
|
|
112
|
+
title: z.string().max(120).optional(),
|
|
113
|
+
about: z.string().max(8000).optional(),
|
|
114
|
+
}),
|
|
115
|
+
execute: ({ title, about }) => attempt(() => inTurn(async () => {
|
|
116
|
+
doing("set_profile");
|
|
117
|
+
await updateProject(userId, projectId, {
|
|
118
|
+
brand: {
|
|
119
|
+
...(title !== undefined ? { name: title } : {}),
|
|
120
|
+
...(about !== undefined ? { about } : {}),
|
|
121
|
+
},
|
|
122
|
+
});
|
|
123
|
+
wrote();
|
|
124
|
+
return { ok: true };
|
|
125
|
+
})),
|
|
126
|
+
}),
|
|
127
|
+
set_facts: createTool({
|
|
128
|
+
id: "set_facts",
|
|
129
|
+
description: "Add facts to the project, or update the ones whose label is already there. A fact is a label and a value.",
|
|
130
|
+
inputSchema: z.object({
|
|
131
|
+
facts: z
|
|
132
|
+
.array(z.object({
|
|
133
|
+
label: z.string().min(1).max(80),
|
|
134
|
+
value: z.string().min(1).max(4000),
|
|
135
|
+
type: z.enum(FACT_TYPES).optional().describe("Only when the value's look misleads"),
|
|
136
|
+
}))
|
|
137
|
+
.min(1),
|
|
138
|
+
}),
|
|
139
|
+
execute: ({ facts }) => attempt(() => inTurn(async () => {
|
|
140
|
+
doing("set_facts");
|
|
141
|
+
const p = await ownedProject(userId, projectId);
|
|
142
|
+
const next = [...p.facts];
|
|
143
|
+
for (const f of facts) {
|
|
144
|
+
const label = f.label.trim();
|
|
145
|
+
const at = next.findIndex((x) => x.label.toLowerCase() === label.toLowerCase());
|
|
146
|
+
if (at >= 0) {
|
|
147
|
+
next[at] = { ...next[at], value: f.value.trim(), type: f.type ?? next[at].type };
|
|
148
|
+
}
|
|
149
|
+
else {
|
|
150
|
+
const key = factKey(label, next.map((x) => x.key));
|
|
151
|
+
next.push({
|
|
152
|
+
key,
|
|
153
|
+
label,
|
|
154
|
+
value: f.value.trim(),
|
|
155
|
+
...(f.type ? { type: f.type } : {}),
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
if (next.length > PROJECT_LIMITS.facts) {
|
|
160
|
+
throw new Error(`A project holds at most ${PROJECT_LIMITS.facts} facts.`);
|
|
161
|
+
}
|
|
162
|
+
await updateProject(userId, projectId, { facts: next });
|
|
163
|
+
wrote();
|
|
164
|
+
return { ok: true, facts: next.length };
|
|
165
|
+
})),
|
|
166
|
+
}),
|
|
167
|
+
remove_facts: createTool({
|
|
168
|
+
id: "remove_facts",
|
|
169
|
+
description: "Remove facts by their labels. Only when the admin asks or a fact is wrong.",
|
|
170
|
+
inputSchema: z.object({ labels: z.array(z.string()).min(1) }),
|
|
171
|
+
execute: ({ labels }) => attempt(() => inTurn(async () => {
|
|
172
|
+
doing("remove_facts");
|
|
173
|
+
const p = await ownedProject(userId, projectId);
|
|
174
|
+
const gone = new Set(labels.map((l) => l.trim().toLowerCase()));
|
|
175
|
+
const next = p.facts.filter((f) => !gone.has(f.label.toLowerCase()));
|
|
176
|
+
await updateProject(userId, projectId, { facts: next });
|
|
177
|
+
wrote();
|
|
178
|
+
return { ok: true, removed: p.facts.length - next.length };
|
|
179
|
+
})),
|
|
180
|
+
}),
|
|
181
|
+
set_colors: createTool({
|
|
182
|
+
id: "set_colors",
|
|
183
|
+
description: "Set the brand's colours — the whole list, the main colour first. Values as #rrggbb.",
|
|
184
|
+
inputSchema: z.object({ colors: z.array(brandColorSchema).max(PROJECT_LIMITS.colors) }),
|
|
185
|
+
execute: ({ colors }) => attempt(() => inTurn(async () => {
|
|
186
|
+
doing("set_colors");
|
|
187
|
+
await updateProject(userId, projectId, {
|
|
188
|
+
brand: { colors: colors.map((c) => ({ ...c, value: c.value.toLowerCase() })) },
|
|
189
|
+
});
|
|
190
|
+
wrote();
|
|
191
|
+
return { ok: true };
|
|
192
|
+
})),
|
|
193
|
+
}),
|
|
194
|
+
save_file: createTool({
|
|
195
|
+
id: "save_file",
|
|
196
|
+
description: "Fetch a file from a public address into the project: a logo, an asset (image, graphic, video) or a document (PDF, Word, Excel, CSV, text). Documents are read and indexed afterwards.",
|
|
197
|
+
inputSchema: z.object({
|
|
198
|
+
url: z.string(),
|
|
199
|
+
kind: z.enum(PROJECT_FILE_KINDS),
|
|
200
|
+
description: z
|
|
201
|
+
.string()
|
|
202
|
+
.max(600)
|
|
203
|
+
.optional()
|
|
204
|
+
.describe("One sentence: what it shows or holds, and what it suits"),
|
|
205
|
+
}),
|
|
206
|
+
execute: ({ url, kind, description }) => attempt(async () => {
|
|
207
|
+
doing("save_file", { kind });
|
|
208
|
+
const res = await safeFetch(url, {
|
|
209
|
+
headers: { "user-agent": "Mozilla/5.0 (compatible; engenty-wizards/0.1)" },
|
|
210
|
+
signal: AbortSignal.any([AbortSignal.timeout(60_000), ...(signal ? [signal] : [])]),
|
|
211
|
+
});
|
|
212
|
+
if (!res.ok) {
|
|
213
|
+
throw new Error(`The address answered ${res.status}.`);
|
|
214
|
+
}
|
|
215
|
+
const mime = (res.headers.get("content-type") ?? "").split(";")[0].trim().toLowerCase();
|
|
216
|
+
if (Number(res.headers.get("content-length") ?? 0) > projectFileLimit(kind, mime)) {
|
|
217
|
+
throw new Error("The file is too large.");
|
|
218
|
+
}
|
|
219
|
+
const data = new Uint8Array(await res.arrayBuffer());
|
|
220
|
+
const name = decodeURIComponent(new URL(res.url).pathname.split("/").pop() || kind);
|
|
221
|
+
const row = await addProjectFile(projectId, {
|
|
222
|
+
kind,
|
|
223
|
+
name,
|
|
224
|
+
mime: mime === "text/html" && kind !== "document" ? "" : mime,
|
|
225
|
+
data,
|
|
226
|
+
description,
|
|
227
|
+
source: res.url,
|
|
228
|
+
});
|
|
229
|
+
wrote();
|
|
230
|
+
return { ok: true, id: row.id, name: row.name, mime: row.mime, size: row.size };
|
|
231
|
+
}),
|
|
232
|
+
}),
|
|
233
|
+
describe_file: createTool({
|
|
234
|
+
id: "describe_file",
|
|
235
|
+
description: "Set the description of one of the project's files.",
|
|
236
|
+
inputSchema: z.object({ id: z.string(), description: z.string().max(600) }),
|
|
237
|
+
execute: ({ id, description }) => attempt(async () => {
|
|
238
|
+
doing("describe_file");
|
|
239
|
+
await updateProjectFile(projectId, id, { description });
|
|
240
|
+
wrote();
|
|
241
|
+
return { ok: true };
|
|
242
|
+
}),
|
|
243
|
+
}),
|
|
244
|
+
look_at_file: createTool({
|
|
245
|
+
id: "look_at_file",
|
|
246
|
+
description: "Look at one of the project's files: a picture is shown to you, a document comes as text.",
|
|
247
|
+
inputSchema: z.object({
|
|
248
|
+
id: z.string(),
|
|
249
|
+
from: z.number().int().min(0).optional().describe("Documents: character to start at"),
|
|
250
|
+
}),
|
|
251
|
+
execute: ({ id, from }) => attempt(async () => {
|
|
252
|
+
doing("look_at_file");
|
|
253
|
+
const { row, data } = await projectFileContent(projectId, id);
|
|
254
|
+
if (row.kind === "document") {
|
|
255
|
+
const text = await documentText(row);
|
|
256
|
+
if (!text) {
|
|
257
|
+
return { name: row.name, note: `The document is not read yet (${row.status}).` };
|
|
258
|
+
}
|
|
259
|
+
const start = Math.min(from ?? 0, text.length);
|
|
260
|
+
return {
|
|
261
|
+
name: row.name,
|
|
262
|
+
text: text.slice(start, start + 12_000),
|
|
263
|
+
next: start + 12_000 < text.length ? start + 12_000 : null,
|
|
264
|
+
};
|
|
265
|
+
}
|
|
266
|
+
if (!/^image\/(png|jpeg|webp|gif)$/.test(row.mime) || data.byteLength > 5_000_000) {
|
|
267
|
+
return { name: row.name, mime: row.mime, note: "This file cannot be shown to you." };
|
|
268
|
+
}
|
|
269
|
+
return { name: row.name, image: data.toString("base64"), mime: row.mime };
|
|
270
|
+
}),
|
|
271
|
+
toModelOutput: (out) => {
|
|
272
|
+
if (!out?.image) {
|
|
273
|
+
return { type: "json", value: out };
|
|
274
|
+
}
|
|
275
|
+
return {
|
|
276
|
+
type: "content",
|
|
277
|
+
value: [
|
|
278
|
+
{ type: "text", text: out.name },
|
|
279
|
+
{ type: "image-data", data: out.image, mediaType: out.mime },
|
|
280
|
+
],
|
|
281
|
+
};
|
|
282
|
+
},
|
|
283
|
+
}),
|
|
284
|
+
};
|
|
285
|
+
if (resolved.vendor === "anthropic") {
|
|
286
|
+
tools.web_search = anthropic.tools.webSearch_20250305({ maxUses: 5 });
|
|
287
|
+
}
|
|
288
|
+
else if (resolved.gateway) {
|
|
289
|
+
tools.web_search = gatewayTools.perplexitySearch({ maxResults: 6 });
|
|
290
|
+
}
|
|
291
|
+
return tools;
|
|
292
|
+
}
|
|
293
|
+
/** One turn with the project assistant: it reads what the admin gave and fills the project in. */
|
|
294
|
+
export async function runProjectAssistant(input) {
|
|
295
|
+
let changed = false;
|
|
296
|
+
const resolved = await textModel("high");
|
|
297
|
+
const tools = buildTools(input, resolved, () => {
|
|
298
|
+
changed = true;
|
|
299
|
+
input.onChanged?.();
|
|
300
|
+
});
|
|
301
|
+
attachTools(resolved, tools);
|
|
302
|
+
const agent = new Agent({
|
|
303
|
+
id: "project-assistant",
|
|
304
|
+
name: "Project assistant",
|
|
305
|
+
instructions: {
|
|
306
|
+
role: "system",
|
|
307
|
+
content: SYSTEM,
|
|
308
|
+
providerOptions: { anthropic: { cacheControl: { type: "ephemeral" } } },
|
|
309
|
+
},
|
|
310
|
+
model: resolved.model,
|
|
311
|
+
tools,
|
|
312
|
+
});
|
|
313
|
+
const context = [
|
|
314
|
+
`The project now:\n\`\`\`json\n${JSON.stringify(await projectState(input.userId, input.projectId))}\n\`\`\``,
|
|
315
|
+
input.message,
|
|
316
|
+
].join("\n\n");
|
|
317
|
+
const stream = await agent.stream([
|
|
318
|
+
...input.history
|
|
319
|
+
.slice(-12)
|
|
320
|
+
.map((m) => m.role === "user"
|
|
321
|
+
? { role: "user", content: m.content }
|
|
322
|
+
: { role: "assistant", content: m.content }),
|
|
323
|
+
{ role: "user", content: context },
|
|
324
|
+
], {
|
|
325
|
+
maxSteps: MAX_STEPS,
|
|
326
|
+
abortSignal: input.signal,
|
|
327
|
+
modelSettings: { maxOutputTokens: 8000 },
|
|
328
|
+
});
|
|
329
|
+
let reply = "";
|
|
330
|
+
for await (const chunk of stream.fullStream) {
|
|
331
|
+
const payload = chunk.payload ?? chunk;
|
|
332
|
+
if (chunk.type === "text-delta") {
|
|
333
|
+
const text = payload.text ?? payload.delta ?? "";
|
|
334
|
+
reply += text;
|
|
335
|
+
input.onText?.(text);
|
|
336
|
+
}
|
|
337
|
+
else if (chunk.type === "tool-call") {
|
|
338
|
+
// The model's own search has no tool of ours that could say so.
|
|
339
|
+
if (payload.toolName === "web_search") {
|
|
340
|
+
input.onActivity?.("Sucht im Web …");
|
|
341
|
+
}
|
|
342
|
+
// Keep what it says before and after a tool call apart.
|
|
343
|
+
if (reply.trim() && !reply.endsWith("\n\n")) {
|
|
344
|
+
input.onText?.("\n\n");
|
|
345
|
+
reply += "\n\n";
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
else if (chunk.type === "error") {
|
|
349
|
+
throw payload.error ?? new Error("assistant stream error");
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
const usage = (await stream.totalUsage) ?? (await stream.usage);
|
|
353
|
+
return { reply: reply.trim(), changed, costUsd: costOf(resolved, usage) };
|
|
354
|
+
}
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
import { mkdirSync, writeFileSync } from "node:fs";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { createInterface } from "node:readline";
|
|
5
|
+
import { internalKey } from "../auth/keys.js";
|
|
6
|
+
import { env } from "../env.js";
|
|
7
|
+
import { CLAUDE_BIN as CLAUDE, claudeEnv } from "../harness/claude.js";
|
|
8
|
+
import { detectHarness, signedOut } from "../harness/index.js";
|
|
9
|
+
import { localModelSettings } from "../models.js";
|
|
10
|
+
import { ServiceError } from "../services/errors.js";
|
|
11
|
+
import { deleteSetting, readSetting, writeSetting } from "../settings.js";
|
|
12
|
+
import { thoughtLine } from "./thought.js";
|
|
13
|
+
/** The AI clients installed on this machine whose subscription can answer the studio chat. */
|
|
14
|
+
export async function subscriptionClients() {
|
|
15
|
+
return (await detectHarness("claude"))?.version ? ["claude"] : [];
|
|
16
|
+
}
|
|
17
|
+
const workDir = join(env.dataDir, "subscription");
|
|
18
|
+
/** The MCP config handed to the client: this server only, signed in with a key made for this process. */
|
|
19
|
+
function mcpConfig() {
|
|
20
|
+
mkdirSync(workDir, { recursive: true });
|
|
21
|
+
const file = join(workDir, "mcp.json");
|
|
22
|
+
writeFileSync(file, JSON.stringify({
|
|
23
|
+
mcpServers: {
|
|
24
|
+
wizards: {
|
|
25
|
+
type: "http",
|
|
26
|
+
url: `http://127.0.0.1:${env.port}/api/mcp`,
|
|
27
|
+
headers: { Authorization: `Bearer ${internalKey()}` },
|
|
28
|
+
},
|
|
29
|
+
},
|
|
30
|
+
}), { mode: 0o600 });
|
|
31
|
+
return file;
|
|
32
|
+
}
|
|
33
|
+
const SYSTEM = (wizardId) => [
|
|
34
|
+
"You are the architect in the engenty wizards studio. The admin talks to you in the studio's chat panel, next to a diagram of the wizard.",
|
|
35
|
+
`You work on exactly one wizard: id "${wizardId}". Read it with get_wizard before you change it; change it with edit_wizard and the file tools; never create another wizard and never publish unless the admin asks.`,
|
|
36
|
+
"Call get_authoring_guide once before your first change. Fix every issue a write reports before you answer.",
|
|
37
|
+
"Answer in the admin's language, in two or three plain sentences: what you changed and what to try next. No JSON, no tool names.",
|
|
38
|
+
].join("\n");
|
|
39
|
+
const BUILDING = /edit_wizard|write_wizard|write_file|delete_file|create_wizard/;
|
|
40
|
+
const ACTIVITY = [
|
|
41
|
+
[/get_authoring_guide/, "Liest die Bauanleitung"],
|
|
42
|
+
[/get_starter|list_starters/, "Sieht sich eine Vorlage an"],
|
|
43
|
+
[/get_wizard|list_files|read_file/, "Liest den Wizard"],
|
|
44
|
+
[/write_file|delete_file/, "Schreibt Dateien"],
|
|
45
|
+
[/edit_wizard|write_wizard/, "Baut den Wizard um"],
|
|
46
|
+
[/check_widget|preview/, "Prüft das Widget"],
|
|
47
|
+
[/start_test_run|get_test_run/, "Testet den Wizard"],
|
|
48
|
+
[/find_connectors|import_connector/, "Sucht einen Dienst"],
|
|
49
|
+
];
|
|
50
|
+
/** The activity for one tool call, naming the file or service it is about when it says. */
|
|
51
|
+
function activity(name, args) {
|
|
52
|
+
const label = ACTIVITY.find(([re]) => re.test(name))?.[1] ?? "Arbeitet";
|
|
53
|
+
const about = [args.path, args.query].find((v) => typeof v === "string" && v.trim());
|
|
54
|
+
return about ? `${label}: ${String(about)} …` : `${label} …`;
|
|
55
|
+
}
|
|
56
|
+
const sessionKey = (wizardId) => `chat-session:${wizardId}`;
|
|
57
|
+
/** One chat turn on the Claude subscription. The client keeps the conversation; we keep its id. */
|
|
58
|
+
export async function subscriptionTurn(input) {
|
|
59
|
+
const session = await readSetting(sessionKey(input.wizardId));
|
|
60
|
+
const args = [
|
|
61
|
+
// The message comes on stdin, so pictures can go with it.
|
|
62
|
+
"-p",
|
|
63
|
+
"--input-format",
|
|
64
|
+
"stream-json",
|
|
65
|
+
"--output-format",
|
|
66
|
+
"stream-json",
|
|
67
|
+
"--verbose",
|
|
68
|
+
// Names a tool as it starts and streams the thinking; whole messages still follow.
|
|
69
|
+
"--include-partial-messages",
|
|
70
|
+
// No built-in tools: `--allowedTools` alone would not take them away.
|
|
71
|
+
"--tools",
|
|
72
|
+
"",
|
|
73
|
+
"--strict-mcp-config",
|
|
74
|
+
"--mcp-config",
|
|
75
|
+
mcpConfig(),
|
|
76
|
+
"--allowedTools",
|
|
77
|
+
"mcp__wizards",
|
|
78
|
+
"--disable-slash-commands",
|
|
79
|
+
"--setting-sources",
|
|
80
|
+
"",
|
|
81
|
+
"--append-system-prompt",
|
|
82
|
+
SYSTEM(input.wizardId),
|
|
83
|
+
...(session ? ["--resume", session] : []),
|
|
84
|
+
];
|
|
85
|
+
const child = spawn(CLAUDE, args, {
|
|
86
|
+
cwd: workDir,
|
|
87
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
88
|
+
signal: input.signal,
|
|
89
|
+
// The client's own sign-in, as the person's terminal has it; no key of ours reaches it.
|
|
90
|
+
env: await claudeEnv(),
|
|
91
|
+
});
|
|
92
|
+
child.stdin.on("error", () => {
|
|
93
|
+
// A client that is gone already says so on stdout and in its exit code.
|
|
94
|
+
});
|
|
95
|
+
child.stdin.end(`${JSON.stringify({
|
|
96
|
+
type: "user",
|
|
97
|
+
message: {
|
|
98
|
+
role: "user",
|
|
99
|
+
content: [
|
|
100
|
+
{ type: "text", text: input.message },
|
|
101
|
+
...(input.images ?? []).map((i) => ({
|
|
102
|
+
type: "image",
|
|
103
|
+
source: {
|
|
104
|
+
type: "base64",
|
|
105
|
+
media_type: i.mime,
|
|
106
|
+
data: Buffer.from(i.data).toString("base64"),
|
|
107
|
+
},
|
|
108
|
+
})),
|
|
109
|
+
],
|
|
110
|
+
},
|
|
111
|
+
})}\n`);
|
|
112
|
+
let reply = "";
|
|
113
|
+
let changed = false;
|
|
114
|
+
let failure = "";
|
|
115
|
+
let thinking = "";
|
|
116
|
+
let thought = null;
|
|
117
|
+
let stderr = "";
|
|
118
|
+
child.stderr.on("data", (chunk) => {
|
|
119
|
+
stderr = (stderr + String(chunk)).slice(-2000);
|
|
120
|
+
});
|
|
121
|
+
for await (const line of createInterface({ input: child.stdout })) {
|
|
122
|
+
let event;
|
|
123
|
+
try {
|
|
124
|
+
event = JSON.parse(line);
|
|
125
|
+
}
|
|
126
|
+
catch {
|
|
127
|
+
continue;
|
|
128
|
+
}
|
|
129
|
+
if (event.type === "stream_event") {
|
|
130
|
+
const e = event.event ?? {};
|
|
131
|
+
if (e.type === "content_block_start" && e.content_block?.type === "tool_use") {
|
|
132
|
+
input.onActivity(activity(String(e.content_block.name ?? ""), {}));
|
|
133
|
+
}
|
|
134
|
+
else if (e.type === "content_block_start" && e.content_block?.type === "thinking") {
|
|
135
|
+
thinking = "";
|
|
136
|
+
}
|
|
137
|
+
else if (e.type === "content_block_delta" && e.delta?.type === "thinking_delta") {
|
|
138
|
+
thinking += e.delta.thinking ?? "";
|
|
139
|
+
const next = thoughtLine(thinking);
|
|
140
|
+
if (next && next !== thought) {
|
|
141
|
+
thought = next;
|
|
142
|
+
input.onThought?.(next);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
else if (event.type === "assistant") {
|
|
147
|
+
for (const block of event.message?.content ?? []) {
|
|
148
|
+
if (block.type === "text" && block.text) {
|
|
149
|
+
const text = reply ? `\n\n${block.text}` : block.text;
|
|
150
|
+
reply += text;
|
|
151
|
+
input.onText(text);
|
|
152
|
+
}
|
|
153
|
+
else if (block.type === "tool_use") {
|
|
154
|
+
const name = String(block.name ?? "");
|
|
155
|
+
if (BUILDING.test(name)) {
|
|
156
|
+
changed = true;
|
|
157
|
+
input.onBuilding();
|
|
158
|
+
}
|
|
159
|
+
input.onActivity(activity(name, block.input ?? {}));
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
else if (event.type === "result") {
|
|
164
|
+
if (event.session_id) {
|
|
165
|
+
await writeSetting(sessionKey(input.wizardId), event.session_id);
|
|
166
|
+
}
|
|
167
|
+
if (event.is_error) {
|
|
168
|
+
failure = String(event.result ?? "error");
|
|
169
|
+
}
|
|
170
|
+
else if (!reply && typeof event.result === "string") {
|
|
171
|
+
reply = event.result;
|
|
172
|
+
input.onText(reply);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
const code = await new Promise((resolve) => child.once("close", resolve));
|
|
177
|
+
if (failure || (code !== 0 && !reply)) {
|
|
178
|
+
// A conversation the client no longer has starts afresh next time.
|
|
179
|
+
await deleteSetting(sessionKey(input.wizardId));
|
|
180
|
+
const detail = failure || stderr || `exit ${code}`;
|
|
181
|
+
if (/authenticat|log ?in|oauth|credential/i.test(detail)) {
|
|
182
|
+
throw new ServiceError("refused", signedOut({ id: "claude", name: "Claude Code" }));
|
|
183
|
+
}
|
|
184
|
+
throw new Error(`Claude Code: ${detail}`.slice(0, 500));
|
|
185
|
+
}
|
|
186
|
+
return { reply: reply.trim(), changed };
|
|
187
|
+
}
|
|
188
|
+
export async function chatEngine(textModelReady) {
|
|
189
|
+
const clients = await subscriptionClients();
|
|
190
|
+
const chosen = await readSetting("chat-engine");
|
|
191
|
+
if (chosen === "claude" && clients.includes("claude")) {
|
|
192
|
+
return "claude";
|
|
193
|
+
}
|
|
194
|
+
if (chosen === "models" && textModelReady) {
|
|
195
|
+
return "models";
|
|
196
|
+
}
|
|
197
|
+
// Nothing chosen: where the client is the source of models, it answers the chat too.
|
|
198
|
+
if (clients.includes("claude") && localModelSettings().source === "claude") {
|
|
199
|
+
return "claude";
|
|
200
|
+
}
|
|
201
|
+
// Else a configured model answers; without one, the subscription does.
|
|
202
|
+
return textModelReady || !clients.includes("claude") ? "models" : "claude";
|
|
203
|
+
}
|
|
204
|
+
export function setChatEngine(engine) {
|
|
205
|
+
return writeSetting("chat-engine", engine);
|
|
206
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The line the studio shows under the activity while the model thinks: the latest heading of
|
|
3
|
+
* its reasoning (`**Planning the table**`), else its latest finished sentence.
|
|
4
|
+
*/
|
|
5
|
+
export function thoughtLine(reasoning) {
|
|
6
|
+
const headings = [...reasoning.matchAll(/\*\*([^*\n]{3,120})\*\*/g)];
|
|
7
|
+
const heading = headings.at(-1)?.[1]?.trim();
|
|
8
|
+
if (heading) {
|
|
9
|
+
return heading;
|
|
10
|
+
}
|
|
11
|
+
const sentences = reasoning
|
|
12
|
+
.replace(/\s+/g, " ")
|
|
13
|
+
.match(/[^.!?]+[.!?](?=\s|$)/g)
|
|
14
|
+
?.map((s) => s.trim())
|
|
15
|
+
.filter((s) => s.length > 3);
|
|
16
|
+
const last = sentences?.at(-1);
|
|
17
|
+
if (!last) {
|
|
18
|
+
return null;
|
|
19
|
+
}
|
|
20
|
+
return last.length > 140 ? `${last.slice(0, 139)}…` : last;
|
|
21
|
+
}
|