cozyclay 2.0.0 → 2.0.2

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/CHANGELOG.md CHANGED
@@ -1,5 +1,61 @@
1
1
  # Changelog
2
2
 
3
+ ## 2.0.2
4
+
5
+ The first-run audit release: `npx cozyclay` is exercised end to end as a new
6
+ user, and the things that broke on that path are fixed. 2.0.1 (the camera
7
+ tutorial refresh and light-mode default, #586) was tagged but never reached
8
+ npm; this release carries it.
9
+
10
+ Agent and package
11
+
12
+ - The Studio Agent panel in the npm package answered every turn with
13
+ "502 — agent unavailable": the sidecar's Studio tools imported `three`
14
+ (a devDependency the package does not ship) through `src/scenes.js`. The
15
+ timeline clock moved to its own module and a test now walks every import
16
+ reachable from `bin/` and fails on any chain to `three`. (#591)
17
+ - A CLIProxy key in the environment no longer leaves the panel on the ChatGPT
18
+ sign-in wall; readiness counts every key-authenticated provider. (#591)
19
+ - `CCLAY_KIMODO_HOST=… npx cozyclay` died at start with
20
+ `Cannot find package 'three'`: the motion bridge now runs from the staged
21
+ MCP runtime (which carries `three`, `tools/` and `dist/models`), and the
22
+ package isolation test keeps it that way. (#591)
23
+ - A live-hub port already taken by another CozyClay is reported on the
24
+ launcher's terminal and named in the pane's error instead of
25
+ "Studio execution is not installed"; agent handler failures are logged
26
+ before the 502; a session id with no record no longer shows
27
+ "Previous conversation could not be restored". (#591)
28
+
29
+ Studio
30
+
31
+ - Create on the start screen opens the selected sample scene under the typed
32
+ name instead of an empty stage named after it; starting over with unsaved
33
+ work asks first. (#591)
34
+ - Reloading a project created from the start screen brought the previous
35
+ session's walking take back onto the character, with "Unsaved changes":
36
+ the hosted-demo seed re-fired on every mount while the bridge probe was
37
+ down. It now skips a saved project (a `?scene=` launch still seeds), and a
38
+ reload opens on the clean checkpoint. (#591)
39
+ - With a take installed, the gizmo and the Transform panel's Position and
40
+ Rotation move the character again; the take keeps animating relative to
41
+ the new placement. (#591)
42
+ - The shot card's hover actions sit above the card, so clicking a short
43
+ shot's name selects it instead of hitting Delete. Draw rail is disabled
44
+ with a reason when no shot is selected. (#591)
45
+
46
+ Tests
47
+
48
+ - The start-screen and project-menu browser suites run against the v2
49
+ shell again, gain the sample-card Create regression, and join the CI
50
+ browser matrix as observation suites; new regressions cover the package
51
+ import graph, CLIProxy readiness, the stage transform and the reload
52
+ session. (#591)
53
+
54
+ ## 2.0.1
55
+
56
+ - The camera tutorial follows the v2 shell and new installs default to the
57
+ light theme. (#586)
58
+
3
59
  ## 2.0.0
4
60
 
5
61
  CozyClay 2.0 is the v2 editor: a floating-glass shell with light and dark
@@ -249,7 +249,12 @@ function liveToolsRuntime(mcpRuntime) {
249
249
  return liveToolsModules(mcpRuntime).then(async ([registry, { startLiveHub }]) => {
250
250
  const owner = liveHubOwner();
251
251
  const token = randomBytes(32).toString("hex");
252
- const liveHub = await startLiveHub(Number(process.env.COZYCLAY_LIVE_PORT ?? 5184), { token, owner });
252
+ const livePort = Number(process.env.COZYCLAY_LIVE_PORT ?? 5184);
253
+ const liveHub = await startLiveHub(livePort, { token, owner });
254
+ // EADDRINUSE comes back as null: another CozyClay (or its dev server) owns
255
+ // the port, and the Studio page is built to connect to that port, so the
256
+ // pane would answer every turn with a misleading capability error.
257
+ if (!liveHub) console.error(`cozyclay: live hub port ${livePort} is already in use (another CozyClay or its dev server?); the Agent pane cannot drive this Studio until that port is free.`);
253
258
  registry.setLiveHub(liveHub);
254
259
  const handlers = registry.createToolHandlers().map((tool) => ({
255
260
  ...tool,
@@ -270,7 +275,7 @@ function liveToolsRuntime(mcpRuntime) {
270
275
  publishLiveEndpoint({ port: liveHub.port, token, owner });
271
276
  liveHub.server?.once("close", () => removeLiveEndpoint(liveHub.port));
272
277
  }
273
- return { liveHub, handlers };
278
+ return { liveHub, handlers, hubPortBusy: !liveHub ? livePort : null };
274
279
  }).catch((error) => {
275
280
  // The pane answers CAPABILITY_MISSING on use; the reason belongs on the
276
281
  // launcher's terminal rather than nowhere.
@@ -425,6 +430,7 @@ export function createAgentHandler({ auth = defaultAuth, codex, models, codexBas
425
430
  await session.modelSession?.abort?.("studio stop", acknowledged ? { quiet: true } : undefined);
426
431
  json(res, 200, { ok: true, status: jobId ? "stopped" : "detached", ...(outcome ? { outcome: { status: outcome.status ?? null, code: outcome.code ?? null, mutated: outcome.mutated ?? null } } : {}) }); return true;
427
432
  }
433
+ if (!studioRuntime && !hub && hubDeps.hubPortBusy) throw new StudioProtocolError("CAPABILITY_MISSING", `The Studio live hub could not start: port ${hubDeps.hubPortBusy} is in use by another CozyClay or its dev server. Free that port and relaunch.`);
428
434
  if (!studioRuntime && (!hub?.command || !hub?.workspaceId)) throw new StudioProtocolError("CAPABILITY_MISSING", "Studio execution is not installed.");
429
435
  if (!value.context.host.workspaceHandle) throw new StudioProtocolError("LIVE_HUB_UNAVAILABLE", "A connected editor handle is required.");
430
436
  let session = studioSessions.get(value.sessionId);
@@ -6,6 +6,7 @@ import { join } from "node:path";
6
6
  import { openBrowser } from "./open-browser.mjs";
7
7
  import { writeSecureJson } from "./agent/secure-file.mjs";
8
8
  import { readKeys } from "./agent/provider-keys.mjs";
9
+ import { PROVIDERS } from "./agent/providers.mjs";
9
10
 
10
11
  export const AUTHORIZE_ENDPOINT = "https://auth.openai.com/oauth/authorize";
11
12
  export const TOKEN_ENDPOINT = "https://auth.openai.com/oauth/token";
@@ -51,7 +52,7 @@ const decodeJwt = (value) => { try { return JSON.parse(Buffer.from(value.split("
51
52
  const claims = () => decodeJwt(readTokens()?.id_token || "");
52
53
  function providersConfigured() {
53
54
  const configured = new Set();
54
- for (const name of ["ANTHROPIC_API_KEY", "OPENAI_API_KEY", "GEMINI_API_KEY", "GOOGLE_API_KEY", "OPENROUTER_API_KEY"]) if (process.env[name]?.trim()) configured.add(name);
55
+ for (const provider of PROVIDERS) for (const name of provider.env) if (process.env[name]?.trim()) configured.add(provider.id);
55
56
  for (const [id, value] of Object.entries(readKeys())) if (typeof value === "string" && value.trim()) configured.add(id);
56
57
  return configured.size;
57
58
  }
package/bin/cozyclay.mjs CHANGED
@@ -407,10 +407,13 @@ process.on("SIGTERM", () => shutdown(143));
407
407
 
408
408
  if (opts.motion && kimodoHost && existsSync(BRIDGE)) {
409
409
  try {
410
+ // The package has no three (a devDependency); the bridge runs from the
411
+ // staged MCP runtime, which carries three, tools/ and src/.
412
+ const bridgeRoot = SOURCE_CHECKOUT ? PKG_ROOT : await ensureMcpRuntime();
410
413
  ({ child: bridge, port: bridgePort } = await startBridge({
411
414
  command: process.execPath,
412
- args: [BRIDGE],
413
- cwd: PKG_ROOT,
415
+ args: [join(bridgeRoot, "tools", "ardy", "bridge.mjs")],
416
+ cwd: bridgeRoot,
414
417
  env: process.env,
415
418
  mainPort: opts.port,
416
419
  onSpawn: (child) => {
@@ -431,6 +434,7 @@ if (opts.motion && kimodoHost && existsSync(BRIDGE)) {
431
434
  }));
432
435
  } catch (err) {
433
436
  console.error(`cozyclay: motion generation sidecar failed: ${err.message}`);
437
+ for (const line of err.lines ?? []) console.error(`cozyclay: ${line}`);
434
438
  console.error("cozyclay: studio did not start; set COZYCLAY_BRIDGE_PORT to an available port or use --no-motion.");
435
439
  if (bridge) await terminateOwned(bridge);
436
440
  process.exit(1);
@@ -455,7 +459,12 @@ server = createServer((req, res) => {
455
459
  return;
456
460
  }
457
461
  if (url.pathname.startsWith("/agent/")) {
458
- void agentHandler(req, res, url.pathname).then((handled) => { if (!handled && !res.writableEnded) { res.writeHead(404); res.end(); } }).catch(() => { if (!res.headersSent) { res.writeHead(502); res.end(JSON.stringify({ error: "agent unavailable" })); } });
462
+ void agentHandler(req, res, url.pathname).then((handled) => { if (!handled && !res.writableEnded) { res.writeHead(404); res.end(); } }).catch((error) => {
463
+ // The pane only sees "502 — agent unavailable"; the reason belongs on
464
+ // the launcher's terminal rather than nowhere.
465
+ console.error(`cozyclay: agent request ${url.pathname} failed: ${error?.message ?? error}`);
466
+ if (!res.headersSent) { res.writeHead(502); res.end(JSON.stringify({ error: "agent unavailable" })); }
467
+ });
459
468
  return;
460
469
  }
461
470
  if (url.pathname === "/__cozyclay/telemetry") {
@@ -18,6 +18,7 @@ const MCP_RUNTIME = process.env.COZYCLAY_MCP_RUNTIME_DIR || join(
18
18
  function probeMcpRuntime(runtime = MCP_RUNTIME) {
19
19
  const server = join(runtime, "mcp", "server.mjs");
20
20
  if (!existsSync(server)) throw new Error("MCP server is missing");
21
+ if (!existsSync(join(runtime, "tools", "ardy", "bridge.mjs"))) throw new Error("motion bridge is missing");
21
22
  const runtimeRequire = createRequire(join(runtime, "package.json"));
22
23
  for (const dependency of ["@modelcontextprotocol/sdk/server/mcp.js", "three", "ws", "zod"]) runtimeRequire.resolve(dependency);
23
24
  return server;
@@ -46,6 +47,15 @@ function installMcpRuntime() {
46
47
  // The server publishes its live endpoint file from this module.
47
48
  copyFileSync(join(PKG_ROOT, "bin", "live-endpoint.mjs"), join(staging, "bin", "live-endpoint.mjs"));
48
49
  cpSync(join(PKG_ROOT, "src"), join(staging, "src"), { recursive: true });
50
+ // The motion bridge imports three, which only this runtime resolves, so the
51
+ // package launches it from here; its out/ scratch dirs are never copied.
52
+ for (const dir of ["ardy", "kimodo", "projflow"]) {
53
+ cpSync(join(PKG_ROOT, "tools", dir), join(staging, "tools", dir), { recursive: true, filter: (path) => basename(path) !== "out" });
54
+ }
55
+ copyFileSync(join(PKG_ROOT, "tools", "process-supervisor.mjs"), join(staging, "tools", "process-supervisor.mjs"));
56
+ // gvhmr-floor.mjs loads its FBX rigs from ../../{public,dist}/models.
57
+ const models = join(PKG_ROOT, "dist", "models");
58
+ if (existsSync(models)) cpSync(models, join(staging, "dist", "models"), { recursive: true });
49
59
 
50
60
  const npm = process.platform === "win32" ? "npm.cmd" : "npm";
51
61
  const child = spawn(npm, ["ci", "--no-audit", "--no-fund"], {
@@ -34,13 +34,13 @@
34
34
  <title>CozyClay Studio — 3D staging and previs in your browser</title>
35
35
 
36
36
  <script defer src="https://static.cloudflareinsights.com/beacon.min.js" data-cf-beacon='{"token": "6a4384d1375d4ffc8a30a6346a8ad1a0"}'></script>
37
- <script type="module" crossorigin src="/assets/app-CA55tcI3.js"></script>
37
+ <script type="module" crossorigin src="/assets/app-BxBLC6GT.js"></script>
38
38
  <link rel="modulepreload" crossorigin href="/assets/rolldown-runtime-hePW80VL.js">
39
39
  <link rel="modulepreload" crossorigin href="/assets/analytics-MVZOEW8O.js">
40
40
  <link rel="modulepreload" crossorigin href="/assets/first-shot-handoff-DgT28ZiV.js">
41
- <link rel="modulepreload" crossorigin href="/assets/shot-prompt-Yrt99wZx.js">
41
+ <link rel="modulepreload" crossorigin href="/assets/shot-prompt-hN0j_O-p.js">
42
42
  <link rel="stylesheet" crossorigin href="/assets/shot-prompt-CbqWcWYi.css">
43
- <link rel="stylesheet" crossorigin href="/assets/app-C1sdVbg4.css">
43
+ <link rel="stylesheet" crossorigin href="/assets/app-CJdxvnrl.css">
44
44
  </head>
45
45
  <body>
46
46
  <div id="root">