@vendoai/vendo 0.4.1 → 0.4.3

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.
@@ -0,0 +1,26 @@
1
+ import type { DevCredential } from "../dev-creds/resolve.js";
2
+ import type { Output } from "./shared.js";
3
+ /** Which provider module the resolved credential will load at runtime.
4
+ The Vendo Cloud gateway speaks the Anthropic-compatible /messages API
5
+ through the host-installed @ai-sdk/anthropic (dev-creds/model.ts). */
6
+ export declare function providerModuleFor(credential: DevCredential): {
7
+ module: string;
8
+ spec: string;
9
+ } | null;
10
+ /** Lockfile-sniffed installer; npm is the fallback. */
11
+ export declare function installCommandFor(root: string): Promise<{
12
+ command: string;
13
+ args: string[];
14
+ }>;
15
+ /** Test seam: resolves to the child's exit code (null on spawn error). */
16
+ export type InstallRunner = (command: string, args: string[], cwd: string) => Promise<number | null>;
17
+ export interface EnsureProviderDepsOptions {
18
+ root: string;
19
+ credential: DevCredential;
20
+ output: Output;
21
+ run?: InstallRunner;
22
+ }
23
+ /** Installs `ai@^6` + the credential's provider when the host can't resolve
24
+ them. Never fatal: a failed install degrades to the exact manual command
25
+ (the same one doctor's E-DEP-001 story names). */
26
+ export declare function ensureProviderDeps(options: EnsureProviderDepsOptions): Promise<void>;
@@ -0,0 +1,100 @@
1
+ import { spawn } from "node:child_process";
2
+ import { readFile } from "node:fs/promises";
3
+ import { join } from "node:path";
4
+ /**
5
+ * The starter model ladder resolves its provider from the HOST's
6
+ * node_modules at runtime (dev-creds/model.ts) — nothing declares
7
+ * `@ai-sdk/*` as a dependency, so a fresh install 500s on the first turn
8
+ * until the user reads the dev-server error and installs it by hand (0.4.1
9
+ * E2E certification finding). Init knows the resolved credential, so it can
10
+ * install exactly the provider that credential needs, up front.
11
+ */
12
+ const PROVIDER_SPECS = {
13
+ anthropic: { module: "@ai-sdk/anthropic", spec: "@ai-sdk/anthropic@^3" },
14
+ openai: { module: "@ai-sdk/openai", spec: "@ai-sdk/openai@^3" },
15
+ google: { module: "@ai-sdk/google", spec: "@ai-sdk/google@^3" },
16
+ };
17
+ const AI_SPEC = "ai@^6";
18
+ /** Which provider module the resolved credential will load at runtime.
19
+ The Vendo Cloud gateway speaks the Anthropic-compatible /messages API
20
+ through the host-installed @ai-sdk/anthropic (dev-creds/model.ts). */
21
+ export function providerModuleFor(credential) {
22
+ if (credential.rung === "env-key")
23
+ return PROVIDER_SPECS[credential.provider];
24
+ if (credential.rung === "vendo-cloud")
25
+ return PROVIDER_SPECS.anthropic;
26
+ return null;
27
+ }
28
+ /** Resolvability is what the runtime ladder checks, so node_modules is the
29
+ evidence — not package.json (a hoisting monorepo satisfies the import
30
+ without a local entry). */
31
+ async function isInstalled(root, moduleName) {
32
+ try {
33
+ await readFile(join(root, "node_modules", ...moduleName.split("/"), "package.json"), "utf8");
34
+ return true;
35
+ }
36
+ catch {
37
+ return false;
38
+ }
39
+ }
40
+ async function fileExists(root, name) {
41
+ try {
42
+ await readFile(join(root, name));
43
+ return true;
44
+ }
45
+ catch {
46
+ return false;
47
+ }
48
+ }
49
+ /** Lockfile-sniffed installer; npm is the fallback. */
50
+ export async function installCommandFor(root) {
51
+ if (await fileExists(root, "pnpm-lock.yaml"))
52
+ return { command: "pnpm", args: ["add"] };
53
+ if (await fileExists(root, "yarn.lock"))
54
+ return { command: "yarn", args: ["add"] };
55
+ if ((await fileExists(root, "bun.lockb")) || (await fileExists(root, "bun.lock"))) {
56
+ return { command: "bun", args: ["add"] };
57
+ }
58
+ return { command: "npm", args: ["install"] };
59
+ }
60
+ const INSTALL_TIMEOUT_MS = 240_000;
61
+ const defaultRunner = (command, args, cwd) => new Promise((resolve) => {
62
+ const child = spawn(command, args, { cwd, stdio: "ignore" });
63
+ const timer = setTimeout(() => {
64
+ child.kill();
65
+ resolve(null);
66
+ }, INSTALL_TIMEOUT_MS);
67
+ child.on("error", () => {
68
+ clearTimeout(timer);
69
+ resolve(null);
70
+ });
71
+ child.on("exit", (code) => {
72
+ clearTimeout(timer);
73
+ resolve(code);
74
+ });
75
+ });
76
+ /** Installs `ai@^6` + the credential's provider when the host can't resolve
77
+ them. Never fatal: a failed install degrades to the exact manual command
78
+ (the same one doctor's E-DEP-001 story names). */
79
+ export async function ensureProviderDeps(options) {
80
+ const provider = providerModuleFor(options.credential);
81
+ if (provider === null)
82
+ return;
83
+ const specs = [];
84
+ if (!(await isInstalled(options.root, "ai")))
85
+ specs.push(AI_SPEC);
86
+ if (!(await isInstalled(options.root, provider.module)))
87
+ specs.push(provider.spec);
88
+ if (specs.length === 0)
89
+ return;
90
+ const { command, args } = await installCommandFor(options.root);
91
+ const invocation = `${command} ${[...args, ...specs].join(" ")}`;
92
+ options.output.log(`Installing the model provider this credential uses: ${specs.join(" ")} (${command})…`);
93
+ const code = await (options.run ?? defaultRunner)(command, [...args, ...specs], options.root);
94
+ if (code === 0) {
95
+ options.output.log(`Installed ${specs.join(" ")}.`);
96
+ }
97
+ else {
98
+ options.output.error(`warning: could not install ${specs.join(" ")} — run \`${invocation}\` yourself before the first turn, or it fails at runtime (E-DEP-001).`);
99
+ }
100
+ }
@@ -1,5 +1,5 @@
1
1
  import { type Telemetry } from "@vendoai/telemetry";
2
- export declare const CLI_VERSION = "0.4.1";
2
+ export declare const CLI_VERSION = "0.4.3";
3
3
  export interface Output {
4
4
  log(message: string): void;
5
5
  error(message: string): void;
@@ -4,7 +4,7 @@ import { dirname, join } from "node:path";
4
4
  import { createInterface } from "node:readline/promises";
5
5
  import { stdin, stdout } from "node:process";
6
6
  import { initTelemetry, repoHost } from "@vendoai/telemetry";
7
- export const CLI_VERSION = "0.4.1";
7
+ export const CLI_VERSION = "0.4.3";
8
8
  export const consoleOutput = {
9
9
  log: (message) => console.log(message),
10
10
  error: (message) => console.error(message),
package/dist/mastra.js CHANGED
@@ -29,6 +29,74 @@ export const VENDO_PRINCIPAL_KEY = "vendo-principal";
29
29
  /** Optional request-context key carrying the host session id into Vendo's
30
30
  * audit trail; unset, the shim mints one per call. */
31
31
  export const VENDO_SESSION_KEY = "vendo-session-id";
32
+ /** The bridge property carrying an OPEN tool's arguments (see
33
+ * {@link isOpenObjectSchema}). */
34
+ const OPEN_ARGS_KEY = "args";
35
+ /**
36
+ * An OPEN tool input: an object schema with no declared properties that
37
+ * accepts arbitrary fields (extraction emits these for routes whose body shape
38
+ * it cannot type, e.g. `{type:"object", properties:{}, additionalProperties:
39
+ * true}`). Mastra's provider schema-compat layers hard-close every object node
40
+ * for strict-mode providers (the OpenAI layer sets `additionalProperties:
41
+ * false` on all of them), so an open schema reaches the model as "this tool
42
+ * takes NO arguments" — and the model dutifully calls it with `{}` even when
43
+ * the user dictated exact args (0.4.x E2E, report-mastra defect 5: the
44
+ * approved call then executed with an empty body). A schema with declared
45
+ * properties survives those transforms, so open inputs ride a single declared
46
+ * JSON-string property instead, unwrapped in execute before the guard.
47
+ */
48
+ function isOpenObjectSchema(schema) {
49
+ if (typeof schema !== "object" || schema === null || Array.isArray(schema))
50
+ return false;
51
+ const record = schema;
52
+ if (record.type !== undefined && record.type !== "object")
53
+ return false;
54
+ const properties = record.properties;
55
+ const declared = typeof properties === "object" && properties !== null
56
+ && Object.keys(properties).length > 0;
57
+ return !declared && record.additionalProperties !== false;
58
+ }
59
+ function openArgsBridgeSchema(tool) {
60
+ return {
61
+ type: "object",
62
+ properties: {
63
+ [OPEN_ARGS_KEY]: {
64
+ // Both forms validate at runtime; strict-mode providers ride the
65
+ // string branch (their compat layer closes every object node), and
66
+ // permissive providers may pass the object directly.
67
+ type: ["string", "object"],
68
+ description: `The arguments to pass to ${tool}: a JSON object, or that object encoded as one JSON string literal (e.g. {"field":"value"}). Include every field the request calls for; pass {} when there are none.`,
69
+ },
70
+ },
71
+ };
72
+ }
73
+ /** Recover the real tool args from a bridged call: a JSON-string `args`
74
+ * (the advertised shape), a plain-object `args` (a model that skipped the
75
+ * encoding), or the raw payload itself (a provider that ignored the bridge). */
76
+ function unwrapOpenArgs(input) {
77
+ if (typeof input !== "object" || input === null || Array.isArray(input))
78
+ return input ?? {};
79
+ const record = input;
80
+ if (OPEN_ARGS_KEY in record) {
81
+ const value = record[OPEN_ARGS_KEY];
82
+ if (value === null || value === undefined)
83
+ return {};
84
+ if (typeof value === "string") {
85
+ const trimmed = value.trim();
86
+ if (trimmed === "")
87
+ return {};
88
+ try {
89
+ return JSON.parse(trimmed);
90
+ }
91
+ catch {
92
+ throw new VendoError("validation", `${OPEN_ARGS_KEY} must be one JSON object literal (e.g. {"field":"value"}); the provided value did not parse as JSON`);
93
+ }
94
+ }
95
+ if (typeof value === "object" && !Array.isArray(value))
96
+ return value;
97
+ }
98
+ return record;
99
+ }
32
100
  function principalFrom(context) {
33
101
  const candidate = context?.requestContext?.get(VENDO_PRINCIPAL_KEY);
34
102
  if (typeof candidate?.kind !== "string" || typeof candidate.subject !== "string") {
@@ -74,11 +142,12 @@ export async function vendoMastraTools(vendo, options) {
74
142
  });
75
143
  const tools = {};
76
144
  for (const entry of pack) {
145
+ const bridged = isOpenObjectSchema(entry.inputSchema);
77
146
  tools[entry.name] = createTool({
78
147
  id: entry.name,
79
148
  description: entry.description,
80
- inputSchema: entry.inputSchema,
81
- execute: (inputData, context) => entry.execute(inputData, {
149
+ inputSchema: bridged ? openArgsBridgeSchema(entry.name) : entry.inputSchema,
150
+ execute: (inputData, context) => entry.execute(bridged ? unwrapOpenArgs(inputData) : inputData, {
82
151
  ctx: runContextFrom(context),
83
152
  ...(context?.agent?.toolCallId === undefined ? {} : { callId: context.agent.toolCallId }),
84
153
  }),
package/dist/react.d.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  import { VendoProvider } from "@vendoai/ui";
2
2
  import { type ComponentProps } from "react";
3
3
  export { createVendoClient, type VendoClient, type VendoClientConfig, VendoProvider, hostComponentMap, useVendoContext, useVendoDiscoverability, useVendoGreeting, useVendoTheme, useVendoTools, type ConnectorOption, type HostComponentsInput, defaultVendoGreeting, type VendoDiscoverability, type VendoGreeting, type ToolMeta, type ToolMetaMap, VendoAppEmbed, VendoApprovalEmbed, VendoToolResult, useActivity, useApp, useApps, useApprovals, useAutomations, useConnections, useConnectorCatalog, useGrants, useMobileTakeover, type MobileTakeover, type PollOptions, useSlotApp, useThreads, useVendoOverlay, type VendoOverlayController, useVendoStatus, useVendoThread, type VendoThreadApproval, ScriptedTransport, type DirectorCue, type DirectorScript, defaultVendoTheme, resolveTheme, themeCssVariables, useVoice, type UseVoiceResult, type OpenSurface, type InClientVenue, type PinDrift, type ShipDiff, type EditResult, type PinRebaseResult, type VersionEntry, type ConnectionAccount, type InitiatedConnection, type RunStatus, type RunRecord, type RunPlan, type AutomationEntry, type EnableResult, type Thread, type ThreadSummary, type GuardPosture, type VendoStatus, } from "@vendoai/ui";
4
+ export { VendoOverlay, type VendoOverlayProps } from "@vendoai/ui/chrome";
4
5
  export { remixable, type RemixableRegistration, type RemixableReportOptions } from "./remixable.js";
5
6
  type ProviderProps = ComponentProps<typeof VendoProvider>;
6
7
  /** 09-vendo §1 — the UI provider prewired to the default wire base. */
package/dist/react.js CHANGED
@@ -23,6 +23,13 @@ useActivity, useApp, useApps, useApprovals, useAutomations, useConnections, useC
23
23
  defaultVendoTheme, resolveTheme, themeCssVariables,
24
24
  // voice/use-voice.ts
25
25
  useVoice, } from "@vendoai/ui";
26
+ // The visible agent surface (launcher pill + panel) — re-exported from the
27
+ // chrome subpath so the init-scaffolded layout wrapper can import everything
28
+ // from "@vendoai/vendo/react": hosts only get @vendoai/vendo as a direct
29
+ // dependency, and under pnpm strict linking the transitive "@vendoai/ui/chrome"
30
+ // does not resolve for them (same TS2307 story as the registry's
31
+ // ComponentRegistry import).
32
+ export { VendoOverlay } from "@vendoai/ui/chrome";
26
33
  export { remixable } from "./remixable.js";
27
34
  /** 09-vendo §1 — the UI provider prewired to the default wire base. */
28
35
  export function VendoRoot(props) {
package/dist/wire/apps.js CHANGED
@@ -1,5 +1,21 @@
1
1
  import { VendoError } from "@vendoai/core";
2
+ import { appStore } from "@vendoai/store";
2
3
  import { json, requestJson, route, string } from "./shared.js";
4
+ /** Unscoped existence probe behind the ?pending=1 disambiguation: does ANY
5
+ principal own a record with this id? Owner-scoped open() answers not-found
6
+ for both "still building" and "exists under someone else", and masking the
7
+ latter as pending is the infinite skeleton of 0.4.1 E2E cert B4 (wire
8
+ principal ≠ chat principal). No document content leaves this check. A
9
+ store that can't answer (hosted wire-door stores have no local db handle)
10
+ keeps the pending window — today's behavior. */
11
+ async function appRecordExists(store, appId) {
12
+ try {
13
+ return (await appStore(store).get(appId)) !== null;
14
+ }
15
+ catch {
16
+ return false;
17
+ }
18
+ }
3
19
  /** 06-apps / 09 §3 — the /apps wire area: CRUD, open/call/edit, history,
4
20
  ship-diff, pin drift/rebase, the gesture fork (fork-pin), export/import,
5
21
  fork (whole-app copy — a different feature from fork-pin). */
@@ -71,13 +87,26 @@ export const appRoutes = [
71
87
  // console 404. Under the additive ?pending=1 flag, ONLY that expected
72
88
  // pre-servable miss becomes a quiet 200 {kind:"pending"}; unflagged
73
89
  // callers keep the contracted 404, and every other failure keeps its
74
- // envelope and status either way.
90
+ // envelope and status either way. A record that DOES exist — just not
91
+ // for this caller — is not a build in progress and never will be: that
92
+ // answers the terminal failed vocabulary (with the principal-mismatch
93
+ // diagnosis) so the embed resolves promptly instead of skeleton-polling
94
+ // to its deadline (0.4.1 E2E cert B4).
75
95
  if (wire.url.searchParams.get("pending") === "1") {
76
96
  try {
77
97
  return json(await deps.apps.open(appId, ctx));
78
98
  }
79
99
  catch (reason) {
80
100
  if (reason instanceof VendoError && reason.code === "not-found") {
101
+ if (await appRecordExists(deps.store, appId)) {
102
+ return json({
103
+ kind: "failed",
104
+ reason: "this app exists but is not visible to this surface's caller — "
105
+ + "the wire route's principal must resolve the same subject your agent loop uses "
106
+ + "(see the principal comment in the generated route, or docs.vendo.run/existing-agents)",
107
+ retryable: false,
108
+ });
109
+ }
81
110
  return json({ kind: "pending" });
82
111
  }
83
112
  throw reason;
@@ -14,7 +14,7 @@ import type { RuntimeCaptureHandler } from "../runtime-capture.js";
14
14
  shares. The anonymous-session + RunContext resolution lives in
15
15
  wire/context.ts; server.ts assembles the table from the per-area modules
16
16
  under src/wire/. */
17
- export declare const VERSION = "0.4.1";
17
+ export declare const VERSION = "0.4.3";
18
18
  export declare const BASE_PATH = "/api/vendo";
19
19
  export type SandboxVenue = "e2b" | "cloud" | "custom" | false;
20
20
  /** How inference is served: "custom" (a host-passed model) or "ladder" (the
@@ -4,7 +4,7 @@ import { VendoError, } from "@vendoai/core";
4
4
  shares. The anonymous-session + RunContext resolution lives in
5
5
  wire/context.ts; server.ts assembles the table from the per-area modules
6
6
  under src/wire/. */
7
- export const VERSION = "0.4.1";
7
+ export const VERSION = "0.4.3";
8
8
  export const BASE_PATH = "/api/vendo";
9
9
  const STATUS_BY_CODE = {
10
10
  validation: 400,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vendoai/vendo",
3
- "version": "0.4.1",
3
+ "version": "0.4.3",
4
4
  "description": "The Vendo umbrella: default composition, public wire routes, React provider, and the vendo CLI.",
5
5
  "keywords": [
6
6
  "ai",
@@ -86,16 +86,16 @@
86
86
  "ajv": "^8.20.0",
87
87
  "ajv-formats": "^3.0.1",
88
88
  "zod": "^3.25.0",
89
- "@vendoai/actions": "0.4.1",
90
- "@vendoai/automations": "0.4.1",
91
- "@vendoai/apps": "0.4.1",
92
- "@vendoai/core": "0.4.1",
93
- "@vendoai/guard": "0.4.1",
94
- "@vendoai/agent": "0.4.1",
95
- "@vendoai/mcp": "0.4.1",
89
+ "@vendoai/actions": "0.4.3",
90
+ "@vendoai/apps": "0.4.3",
91
+ "@vendoai/automations": "0.4.3",
92
+ "@vendoai/agent": "0.4.3",
93
+ "@vendoai/core": "0.4.3",
94
+ "@vendoai/guard": "0.4.3",
96
95
  "@vendoai/telemetry": "0.3.1",
97
- "@vendoai/store": "0.4.1",
98
- "@vendoai/ui": "0.4.1"
96
+ "@vendoai/ui": "0.4.3",
97
+ "@vendoai/mcp": "0.4.3",
98
+ "@vendoai/store": "0.4.3"
99
99
  },
100
100
  "peerDependencies": {
101
101
  "@auth/core": "^0.34.3",