cozyclay 1.6.0 → 1.7.0

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.
Files changed (127) hide show
  1. package/CHANGELOG.md +151 -0
  2. package/README.md +62 -23
  3. package/bin/cozyclay.mjs +35 -22
  4. package/dist/app/index.html +3 -2
  5. package/dist/assets/app-BdCI2Bys.js +4851 -0
  6. package/dist/assets/app-CrB3UF4K.css +1 -0
  7. package/dist/assets/config-Bx7pYsD9.js +1 -0
  8. package/dist/assets/config-DeoFTYzb.css +1 -0
  9. package/dist/assets/demo-DEAy-8Eq.js +1 -0
  10. package/dist/assets/modulepreload-polyfill-P2Xu9kJm.js +1 -0
  11. package/dist/assets/ticket-DbrIKBrJ.js +1 -0
  12. package/dist/cozyclay-package.json +1 -1
  13. package/dist/index.html +8 -10
  14. package/dist/robots.txt +2 -0
  15. package/dist/sw.js +8 -2
  16. package/mcp/ardy-prompts.mjs +39 -9
  17. package/mcp/live-hub.mjs +10 -0
  18. package/mcp/package.json +1 -1
  19. package/mcp/runtime/package-lock.json +1 -1
  20. package/mcp/runtime/package.json +1 -1
  21. package/mcp/server.mjs +124 -22
  22. package/package.json +13 -7
  23. package/src/App.jsx +4699 -1969
  24. package/src/analytics.js +2 -0
  25. package/src/app-stage.jsx +2806 -0
  26. package/src/ardy/export.js +9 -1
  27. package/src/ardy/ik.js +238 -4
  28. package/src/ardy/motion-edit.js +41 -0
  29. package/src/ardy/motion-url.js +55 -0
  30. package/src/ardy/timeline.jsx +1019 -285
  31. package/src/asset-pane.jsx +18 -7
  32. package/src/auto-color.js +63 -0
  33. package/src/burn-in.js +91 -0
  34. package/src/camera-block.js +21 -4
  35. package/src/camera-follow.js +16 -6
  36. package/src/controls.jsx +44 -4
  37. package/src/dualview.jsx +89 -10
  38. package/src/error-boundary.jsx +41 -0
  39. package/src/gizmo-claim.js +48 -0
  40. package/src/grid-floor.jsx +97 -0
  41. package/src/grid-view.js +40 -0
  42. package/src/hierarchy-model.js +124 -71
  43. package/src/hierarchy-panel.jsx +101 -6
  44. package/src/line-edit.js +1348 -0
  45. package/src/live-control.js +11 -1
  46. package/src/main.jsx +4 -1
  47. package/src/matte.js +55 -0
  48. package/src/motion-trail.js +250 -0
  49. package/src/object-gizmo.jsx +57 -18
  50. package/src/object-path.js +224 -0
  51. package/src/planview.jsx +165 -14
  52. package/src/pose-thumbs.js +1 -1
  53. package/src/posestudio.jsx +99 -84
  54. package/src/project-browser.jsx +63 -6
  55. package/src/project.js +44 -0
  56. package/src/props.jsx +169 -27
  57. package/src/result-modal.jsx +2 -14
  58. package/src/room.jsx +37 -10
  59. package/src/scene-asset-cache.js +3 -0
  60. package/src/scene-objects.js +156 -3
  61. package/src/scenes.js +22 -1
  62. package/src/shot-authoring.js +9 -0
  63. package/src/shot-guides.js +90 -0
  64. package/src/shot.js +6 -7
  65. package/src/speed-envelope.js +254 -0
  66. package/src/styles.css +4831 -3436
  67. package/src/take-recipe.js +165 -0
  68. package/src/timeline-extent.js +39 -0
  69. package/src/use-render-activity.js +6 -0
  70. package/tools/ardy/BRIDGE.md +20 -316
  71. package/tools/ardy/README.md +22 -201
  72. package/tools/ardy/bridge.mjs +598 -73
  73. package/tools/ardy/bvh-cskel27.mjs +63 -5
  74. package/tools/ardy/prompt-limits.mjs +5 -0
  75. package/tools/ardy/run-on-box.sh +1 -1
  76. package/tools/ardy/runners/index.mjs +11 -16
  77. package/tools/ardy/visual-qa.mjs +38 -30
  78. package/tools/ardy/vq-car.mjs +1 -1
  79. package/tools/demo-worker/README.md +137 -0
  80. package/tools/demo-worker/api-client.mjs +239 -0
  81. package/tools/demo-worker/generate.mjs +303 -0
  82. package/tools/demo-worker/index.mjs +466 -0
  83. package/tools/demo-worker/sign-core.mjs +112 -0
  84. package/tools/demo-worker/sign.mjs +161 -0
  85. package/tools/dev-full.mjs +54 -36
  86. package/tools/kimodo/compare-seam.mjs +108 -0
  87. package/tools/kimodo/constraints.mjs +244 -0
  88. package/tools/kimodo/edit.mjs +264 -0
  89. package/tools/kimodo/effector-constraints.mjs +200 -0
  90. package/tools/kimodo/generate.mjs +597 -0
  91. package/tools/kimodo/measure-pose.mjs +65 -0
  92. package/tools/kimodo/measure-preserve.mjs +899 -0
  93. package/tools/kimodo/measure-waypoints.mjs +55 -0
  94. package/tools/kimodo/pose-constraints.mjs +250 -0
  95. package/tools/kimodo/preserve-mask.mjs +543 -0
  96. package/tools/kimodo/read-npz.mjs +189 -0
  97. package/tools/kimodo/run-edit-on-box.mjs +155 -0
  98. package/tools/kimodo/run-sequence-on-box.mjs +178 -0
  99. package/tools/kimodo/runner.mjs +246 -0
  100. package/tools/kimodo/seam-batch.mjs +77 -0
  101. package/tools/kimodo/setup-local.mjs +30 -0
  102. package/tools/kimodo/setup-on-box.sh +83 -0
  103. package/tools/kimodo/soma77-to-cskel27.mjs +305 -0
  104. package/tools/process-supervisor.mjs +1 -1
  105. package/tools/projflow/driver.py +1069 -0
  106. package/tools/projflow/generate.mjs +805 -0
  107. package/tools/projflow/hml22-to-cskel27.mjs +735 -0
  108. package/tools/projflow/line-edit-job.mjs +547 -0
  109. package/tools/projflow/replay.mjs +452 -0
  110. package/tools/projflow/runner.mjs +269 -0
  111. package/tools/projflow/service.mjs +742 -0
  112. package/tools/run-tests.mjs +108 -19
  113. package/dist/assets/app-CqOk49Bk.css +0 -1
  114. package/dist/assets/app-G55dPEDB.js +0 -4811
  115. package/src/generation/client.js +0 -56
  116. package/src/generation/generation-request.js +0 -17
  117. package/src/generation/session.js +0 -47
  118. package/src/generation/shot-spec.js +0 -40
  119. package/src/generation/use-generation.js +0 -44
  120. package/tools/generation/README.md +0 -29
  121. package/tools/generation/bridge.mjs +0 -162
  122. package/tools/generation/job-store.mjs +0 -51
  123. package/tools/generation/providers/kling.mjs +0 -76
  124. package/tools/generation/providers/runway.mjs +0 -101
  125. package/tools/generation/providers/seedance.mjs +0 -87
  126. package/tools/generation/providers/shared.mjs +0 -49
  127. package/tools/generation/providers/veo.mjs +0 -84
@@ -0,0 +1,269 @@
1
+ /**
2
+ * runner.mjs — ProjFlow backend for the generation bridge.
3
+ *
4
+ * Mirrors the surface tools/kimodo/runner.mjs exposes ({ mode, describe,
5
+ * probeHealth, ... , <mode>Command }) so bridge.mjs (agent M4, wave 2) can spawn
6
+ * the returned command and grep its `done - <path> (<bytes>)` line exactly as it
7
+ * does for Kimodo. Nothing here talks to the box directly except probeHealth;
8
+ * everything else builds a command for the bridge to spawn.
9
+ *
10
+ * WHAT THIS BACKEND DOES. One thing: line edits. ProjFlow is adopted as an
11
+ * ENGINE-PER-TASK, not an engine-per-session (contract C6) — the bridge routes a
12
+ * `lineEdit` request here regardless of CCLAY_MOTION_BACKEND and keeps sending
13
+ * everything else to Kimodo. So the text-to-motion, sequencing, preserve and
14
+ * base-clip entry points are not implemented and refuse by name rather than
15
+ * quietly producing a take that ignored their inputs.
16
+ *
17
+ * (ProjFlow's checkpoint is a text-conditioned model and CAN generate from a
18
+ * prompt alone, so `singleCommand` is a deliberate refusal, not a missing
19
+ * feature: Kimodo already owns that path, produces rotations rather than
20
+ * positions, and swapping engines under an unchanged request would silently
21
+ * change the take's skeleton fidelity.)
22
+ *
23
+ * WHAT THE COMMAND PRODUCES. The raw hml22 (T,22,3) .npy the box wrote, plus its
24
+ * .meta.json sidecar — NOT a cclay npz. Lifting 22-joint positions to cskel27
25
+ * (contract C8, agent M2) and splicing the edited span back into the source take
26
+ * (agent M5) are separate modules that do not exist yet; wave-2 integration
27
+ * composes them on top of this output. The raw motion is also exactly what a
28
+ * LATER line edit sources from, so keeping it is not scaffolding.
29
+ *
30
+ * ENV. Everything is read at construction so `describe()` can report it:
31
+ *
32
+ * CCLAY_PROJFLOW_HOST the GPU box; falls back to CCLAY_KIMODO_HOST,
33
+ * because one box serves both engines today and making
34
+ * the operator set two variables to the same value is
35
+ * how one of them goes stale.
36
+ * CCLAY_PROJFLOW_REPO the ProjFlow clone (S1 put it at
37
+ * /home/yun/projflow-scout/repo — note `repo`, not
38
+ * `ProjFlow`; the clone was named by the setup script).
39
+ * CCLAY_PROJFLOW_PYTHON the scout venv's python (S1: python3.10 venv, pinned
40
+ * numpy==1.23.5 / timm==1.0.9 — the system python
41
+ * cannot run the repo).
42
+ * CCLAY_PROJFLOW_HOME HOME override for every box run, so CLIP's 338 MB
43
+ * ViT-B/32 download stays inside the scout directory.
44
+ * CCLAY_PROJFLOW_STEPS / _RIDGE / _PRESERVE_STRIDE / _PRESERVE_MARGIN
45
+ * tunables read by generate.mjs itself.
46
+ * CCLAY_PROJFLOW_RESIDENT=0
47
+ * turn the warm service off (contract C11); every
48
+ * edit then takes the cold path it always had.
49
+ */
50
+
51
+ import { spawn } from "node:child_process";
52
+ import { dirname, join } from "node:path";
53
+ import { fileURLToPath } from "node:url";
54
+
55
+ import { peekResidentService, residentEnabled } from "./service.mjs";
56
+
57
+ const HERE = dirname(fileURLToPath(import.meta.url));
58
+ /** generate.mjs doubles as the box wrapper; see the CLI note at the bottom of
59
+ * that file for why ProjFlow has one script where Kimodo has two. */
60
+ const RUN_LINE_EDIT = join(HERE, "generate.mjs");
61
+
62
+ const SSH_OPTS = ["-o", "BatchMode=yes", "-o", "ConnectTimeout=10"];
63
+
64
+ export const DEFAULT_REPO = "/home/yun/projflow-scout/repo";
65
+ export const DEFAULT_PYTHON = "/home/yun/projflow-scout/venv/bin/python";
66
+ export const DEFAULT_BOX_HOME = "/home/yun/projflow-scout/home";
67
+
68
+ function run(argv, timeoutMs = 60_000) {
69
+ return new Promise((resolve) => {
70
+ const child = spawn(argv[0], argv.slice(1), { stdio: ["ignore", "pipe", "pipe"] });
71
+ let stdout = "";
72
+ let stderr = "";
73
+ const timer = setTimeout(() => child.kill("SIGKILL"), timeoutMs);
74
+ child.stdout.on("data", (chunk) => (stdout += chunk));
75
+ child.stderr.on("data", (chunk) => (stderr += chunk));
76
+ child.on("close", (code) => {
77
+ clearTimeout(timer);
78
+ resolve({ code, stdout, stderr });
79
+ });
80
+ });
81
+ }
82
+
83
+ /**
84
+ * Where a run whose bridge output is `outputPath` keeps the RAW hml22 motion.
85
+ *
86
+ * Same role, and the same one-place-decides rule, as Kimodo's
87
+ * nativeMotionPath: generate.mjs writes the file, a later edit finds it again.
88
+ * The suffix says which engine wrote it because the two formats are not
89
+ * interchangeable — Kimodo's is a 77-joint rotation npz, this is a 22-joint
90
+ * position npy.
91
+ */
92
+ export function nativeMotionPath(outputPath) {
93
+ return `${String(outputPath).replace(/\.(npz|npy)$/i, "")}.projflow.npy`;
94
+ }
95
+
96
+ /** The metadata sidecar driver.py wrote beside a native motion: row count,
97
+ * sampling seconds, and the exactness numbers gates GP1/GP2 read. */
98
+ export function metaPath(nativePath) {
99
+ return `${String(nativePath).replace(/\.npy$/i, "")}.meta.json`;
100
+ }
101
+
102
+ function unsupported(feature) {
103
+ return () => {
104
+ throw new Error(
105
+ `the ProjFlow backend only implements line edits; ${feature} stays on the Kimodo backend`
106
+ );
107
+ };
108
+ }
109
+
110
+ export function createProjflowRunner() {
111
+ const HOST = process.env.CCLAY_PROJFLOW_HOST || process.env.CCLAY_KIMODO_HOST || "";
112
+ const REPO = process.env.CCLAY_PROJFLOW_REPO || DEFAULT_REPO;
113
+ const PYTHON = process.env.CCLAY_PROJFLOW_PYTHON || DEFAULT_PYTHON;
114
+ const BOX_HOME = process.env.CCLAY_PROJFLOW_HOME || DEFAULT_BOX_HOME;
115
+
116
+ if (!HOST) {
117
+ throw new Error(
118
+ "CCLAY_PROJFLOW_HOST (or CCLAY_KIMODO_HOST) is required for the ProjFlow backend (for example: user@gpu-box)"
119
+ );
120
+ }
121
+
122
+ /**
123
+ * Can the box actually run a line edit?
124
+ *
125
+ * The check is "does the venv python import the REPO", not "is there a
126
+ * python": S1's install needed pinned numpy 1.23.5 (1.24+ dies on
127
+ * `np.finfo(np.float)` in utils/quaternion.py) and timm 1.0.9 (newer timm
128
+ * passes `device=` into the norm layer and blows up at models/ACMDM.py:467).
129
+ * Both failures happen at IMPORT time, so importing models.ACMDM is exactly
130
+ * the check that separates "the env is fine" from "the next real run will die
131
+ * six seconds in". The checkpoint's presence is verified too, because a
132
+ * missing 625 MB tar is the other way a healthy-looking box fails a run.
133
+ */
134
+ async function probeHealth() {
135
+ const script = [
136
+ "import sys, os, torch",
137
+ "import models.ACMDM as m",
138
+ "ck = os.path.join('checkpoints', 't2m', 'ACMDM_Raw_Flow_S_PatchSize22', 'model', 'latest.tar')",
139
+ "print('device=' + ('cuda:0' if torch.cuda.is_available() else 'cpu'))",
140
+ "print('models=%d' % len(m.ACMDM_models))",
141
+ "print('checkpoint=%s' % ('yes' if os.path.exists(ck) else 'no'))",
142
+ ].join("; ");
143
+ const remote = [
144
+ `cd ${JSON.stringify(REPO)}`,
145
+ `HOME=${BOX_HOME} ${PYTHON} -c ${JSON.stringify(script)}`,
146
+ ].join(" && ");
147
+ const { code, stdout, stderr } = await run(["ssh", ...SSH_OPTS, HOST, remote], 120_000);
148
+ if (code !== 0) {
149
+ throw new Error(
150
+ `ssh probe on ${HOST} failed (exit ${code}): ${stderr.trim().split("\n").pop()}`
151
+ );
152
+ }
153
+ const device = /^device=(.*)$/m.exec(stdout)?.[1] ?? "unknown";
154
+ const checkpoint = /^checkpoint=(.*)$/m.exec(stdout)?.[1] === "yes";
155
+ if (!checkpoint) {
156
+ throw new Error(
157
+ `${HOST}:${REPO} has no ACMDM_Raw_Flow_S_PatchSize22 checkpoint; a line edit would fail after the ssh round trip`
158
+ );
159
+ }
160
+ // No encoder sidecar: CLIP loads in the generation process (S1 measured
161
+ // the whole model load at 3.9 s), so health has nothing to report for it
162
+ // and says so rather than faking a port.
163
+ return {
164
+ ok: true,
165
+ host: HOST,
166
+ repo: REPO,
167
+ encoder: "in-process",
168
+ device,
169
+ checkpoint: true,
170
+ resident: await residentUp(),
171
+ };
172
+ }
173
+
174
+ /**
175
+ * Is a WARM child actually there right now (contract C11)?
176
+ *
177
+ * Deliberately three things it is not. It does not START a resident: a
178
+ * health probe that loads 800 MiB of weights onto the GPU as a side effect
179
+ * would be a surprising answer to "are you well". It does not trust
180
+ * `state === "ready"`, which only says the child announced itself at some
181
+ * point; it sends a ping and waits for the answer, because the interesting
182
+ * failure is a child that died an hour ago. And it never throws — a
183
+ * resident that cannot be reached is `false`, not a failed health check,
184
+ * since every edit still works through the cold path.
185
+ */
186
+ async function residentUp() {
187
+ if (!residentEnabled()) return false;
188
+ const service = peekResidentService({ host: HOST, python: PYTHON, repo: REPO, boxHome: BOX_HOME });
189
+ if (!service || !service.isReady()) return false;
190
+ try {
191
+ const pong = await service.ping();
192
+ return pong?.pong === true;
193
+ } catch {
194
+ return false;
195
+ }
196
+ }
197
+
198
+ // ProjFlow takes a source motion, not a base clip; there is nothing to list.
199
+ // An empty listing is a valid answer, not a failure, and the bridge already
200
+ // tolerates it for Kimodo.
201
+ async function listBases() {
202
+ return [];
203
+ }
204
+
205
+ /** The environment the box wrapper inherits. Every key is set explicitly and
206
+ * the run-scoped one is DELETED when unused: process.env is inherited from a
207
+ * long-lived sidecar, and a leftover output path would have the next request
208
+ * silently overwrite the previous take. */
209
+ function boxEnv({ output } = {}) {
210
+ const env = {
211
+ ...process.env,
212
+ CCLAY_PROJFLOW_HOST: HOST,
213
+ CCLAY_PROJFLOW_REPO: REPO,
214
+ CCLAY_PROJFLOW_PYTHON: PYTHON,
215
+ CCLAY_PROJFLOW_HOME: BOX_HOME,
216
+ };
217
+ delete env.CCLAY_PROJFLOW_NATIVE_OUT;
218
+ if (output) env.CCLAY_PROJFLOW_NATIVE_OUT = nativeMotionPath(output);
219
+ return env;
220
+ }
221
+
222
+ /** Which artifact of an earlier run this backend can edit, or null when that
223
+ * take has none — it came from Kimodo, or predates line editing. The bridge
224
+ * refuses the edit and says so rather than converting a lossy cskel27 file
225
+ * back into positions. */
226
+ function sourceMotionFor(motionPath) {
227
+ return nativeMotionPath(motionPath);
228
+ }
229
+
230
+ /**
231
+ * The one command this backend builds.
232
+ *
233
+ * `line` is a C6 request; it travels as a FILE rather than on argv because a
234
+ * 20-point polyline plus a 3x3 rotation is not a command line, and because the
235
+ * bridge already writes per-run JSON for Kimodo's constraints. The caller
236
+ * writes it; this function only names it.
237
+ */
238
+ function lineEditCommand({ source, line, output, steps, preview = false, seed }) {
239
+ if (!source) throw new Error("lineEditCommand: source (an hml22 .npy take) is required");
240
+ if (!line) throw new Error("lineEditCommand: line (a path to the C6 request JSON) is required");
241
+ if (!output) throw new Error("lineEditCommand: output is required");
242
+ const args = [RUN_LINE_EDIT, "--source", source, "--line", line, "--output", nativeMotionPath(output)];
243
+ if (preview) args.push("--preview");
244
+ else if (Number.isInteger(steps)) args.push("--steps", String(steps));
245
+ if (Number.isInteger(seed)) args.push("--seed", String(seed));
246
+ return {
247
+ command: process.execPath,
248
+ args,
249
+ env: boxEnv({ output }),
250
+ doneRe: /^run-projflow-line-edit: done - (.+) \((\d+) bytes\)$/,
251
+ label: "run-projflow-line-edit",
252
+ };
253
+ }
254
+
255
+ return {
256
+ mode: "projflow",
257
+ describe: () =>
258
+ `box ${HOST} (repo ${REPO}, ACMDM_Raw_Flow_S_PatchSize22, 22-joint positions @ 20 fps)`,
259
+ probeHealth,
260
+ listBases,
261
+ sourceMotionFor,
262
+ lineEditCommand,
263
+ // Named refusals, so a mis-routed request reads as a routing bug rather
264
+ // than a crash inside an unrelated module.
265
+ singleCommand: unsupported("text-to-motion generation"),
266
+ sequenceCommand: unsupported("multi-segment sequencing"),
267
+ editCommand: unsupported("prompt-based span regeneration"),
268
+ };
269
+ }