cozyclay 3.1.2 → 3.1.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.
Files changed (112) hide show
  1. package/CHANGELOG.md +25 -0
  2. package/README.md +1 -1
  3. package/bin/agent/agent-runner.mjs +160 -151
  4. package/bin/agent/agent-stream.mjs +86 -0
  5. package/bin/agent/anthropic-subscription-events.mjs +120 -0
  6. package/bin/agent/anthropic-subscription-prompt.mjs +116 -0
  7. package/bin/agent/anthropic-subscription-stream.mjs +251 -0
  8. package/bin/agent/anthropic-subscription.mjs +73 -0
  9. package/bin/agent/claude-code-executable.mjs +93 -0
  10. package/bin/agent/cliproxy-provider.mjs +25 -11
  11. package/bin/agent/model-roles.mjs +112 -0
  12. package/bin/agent/openai-compatible-provider.mjs +95 -0
  13. package/bin/agent/pi-tools.mjs +19 -10
  14. package/bin/agent/provider-login.mjs +3 -1
  15. package/bin/agent/provider-routes.mjs +2 -0
  16. package/bin/agent/providers.mjs +35 -130
  17. package/dist/app/index.html +5 -5
  18. package/dist/assets/TaskBoard-U1Yl-4F1.js +1 -0
  19. package/dist/assets/app-BdRlMf4J.css +1 -0
  20. package/dist/assets/app-DwETmoQZ.js +5144 -0
  21. package/dist/assets/{motion3-compile-dgGhrvPK.js → motion3-compile-DDG3wklD.js} +1 -1
  22. package/dist/assets/scene-asset-sync-Bz59Ghdz.js +13 -0
  23. package/dist/assets/task-board-DFfNa0xg.js +15 -0
  24. package/dist/assets/{workflow-C87p6E1E.js → workflow-BMJGd_gZ.js} +1 -1
  25. package/dist/cozyclay-package.json +1 -1
  26. package/dist/workflow/index.html +3 -3
  27. package/package.json +6 -4
  28. package/src/App.jsx +28 -28
  29. package/src/agent-image-client.js +2 -2
  30. package/src/asset-pane.jsx +14 -14
  31. package/src/character-kind.js +13 -12
  32. package/src/commands/project.js +2 -1
  33. package/src/commands/shot.js +1 -1
  34. package/src/commands/view.js +2 -2
  35. package/src/domains/cast.js +15 -15
  36. package/src/domains/motion/motion-range-pin-rules.js +1 -1
  37. package/src/domains/motion.js +11 -11
  38. package/src/domains/objects.js +20 -30
  39. package/src/domains/scenes.js +14 -21
  40. package/src/domains/shots.js +3 -3
  41. package/src/first-success-guide.jsx +19 -3
  42. package/src/hierarchy-panel.jsx +6 -7
  43. package/src/locale-zh/app.js +124 -0
  44. package/src/locale-zh/domains.js +36 -0
  45. package/src/locale-zh/labels.js +148 -0
  46. package/src/locale-zh/panels.js +225 -0
  47. package/src/locale-zh/shell.js +230 -0
  48. package/src/locale-zh/timeline.js +145 -0
  49. package/src/locale-zh/workflow.js +104 -0
  50. package/src/locale-zh/workspace.js +190 -0
  51. package/src/locale-zh.js +21 -0
  52. package/src/locale.js +16 -6
  53. package/src/main.jsx +1 -1
  54. package/src/motion/beat-board-model.js +4 -4
  55. package/src/motion/beat-board.jsx +1 -1
  56. package/src/motion/ik-pick.js +24 -0
  57. package/src/motion/take-segments.jsx +5 -5
  58. package/src/motion/timeline.jsx +30 -40
  59. package/src/motion/waypoints.js +6 -6
  60. package/src/object-catalog.jsx +4 -5
  61. package/src/panels/CameraPanel.jsx +3 -3
  62. package/src/panels/HdrEnvironmentControls.jsx +2 -1
  63. package/src/panels/ObjectTransformPanel.jsx +19 -29
  64. package/src/panels/PoseImagePanel.jsx +8 -8
  65. package/src/panels/PosePanel.jsx +2 -2
  66. package/src/panels/ReferenceImageField.jsx +2 -2
  67. package/src/panels/RigControlPanel.jsx +2 -2
  68. package/src/panels/SculptPanel.jsx +9 -9
  69. package/src/panels/SubjectBox.jsx +3 -3
  70. package/src/panels/SubjectsPanel.jsx +1 -1
  71. package/src/posestudio.jsx +10 -8
  72. package/src/project-browser.css +7 -6
  73. package/src/project-browser.jsx +7 -6
  74. package/src/project-open-file.js +19 -0
  75. package/src/qa-label.js +1 -1
  76. package/src/range-pin-panel.jsx +14 -14
  77. package/src/resource-status.jsx +3 -3
  78. package/src/result-modal.jsx +10 -10
  79. package/src/settings-menu.jsx +4 -1
  80. package/src/shell/BoardSlot.jsx +5 -5
  81. package/src/shell/CompositionGuideButton.jsx +1 -1
  82. package/src/shell/GhostsMenuGroup.jsx +1 -1
  83. package/src/shell/StatusBar.jsx +4 -4
  84. package/src/shell/ViewportToolbar.jsx +3 -3
  85. package/src/shell/add-camera-shot.js +2 -2
  86. package/src/shell/details-selection.js +1 -1
  87. package/src/shell/preference-controls.jsx +2 -1
  88. package/src/shot-guides.js +5 -5
  89. package/src/stage-hdr.js +3 -3
  90. package/src/stage-labels-ko.js +2 -2
  91. package/src/stage-startup-defaults.js +3 -3
  92. package/src/studio-actions.js +2 -2
  93. package/src/styles/tokens.css +1 -1
  94. package/src/styles.css +1 -1
  95. package/src/use-case-question.jsx +18 -18
  96. package/src/workflow/AgentPanel.jsx +4 -4
  97. package/src/workflow/AgentPlanCard.jsx +1 -1
  98. package/src/workflow/AgentProgressCard.jsx +4 -4
  99. package/src/workflow/AgentTurnSteps.jsx +1 -1
  100. package/src/workflow/LoginPanel.jsx +1 -1
  101. package/src/workflow/TaskBoard.jsx +1 -1
  102. package/src/workflow/TaskInline.jsx +1 -1
  103. package/src/workflow/agent-client.js +1 -1
  104. package/src/workflow/agent-turn.js +5 -5
  105. package/src/workflow/task-board.js +4 -4
  106. package/tools/loc-baseline.json +1 -1
  107. package/tools/run-tests.mjs +4 -4
  108. package/dist/assets/TaskBoard-GR8xZm5c.js +0 -1
  109. package/dist/assets/app-Bhe0TpGz.css +0 -1
  110. package/dist/assets/app-C4QTcDcb.js +0 -5144
  111. package/dist/assets/scene-asset-sync-qoxcdrhj.js +0 -13
  112. package/dist/assets/task-board-BqReHzOi.js +0 -15
@@ -5,6 +5,9 @@ import { createCredentialStore } from "./credential-store.mjs";
5
5
  import { readCredentials } from "./provider-keys.mjs";
6
6
  import { cliproxyBase, cliproxyProvider, extendCliproxyProvider, registryLiveCliproxy } from "./cliproxy-provider.mjs";
7
7
  export { cliproxyLiveModel } from "./cliproxy-provider.mjs";
8
+ import { OPENAI_COMPATIBLE_ID, openaiCompatibleProvider, refreshOpenAICompatible } from "./openai-compatible-provider.mjs";
9
+ import { rolesByMain } from "./model-roles.mjs";
10
+ export { MODEL_ROLES, fallbackChain, resolveRoleModels } from "./model-roles.mjs";
8
11
 
9
12
  // #379 (F2 pass 5 finding 4): pi never forwards a per-request `fetch` to the
10
13
  // codex API module — `pi-agent-core`'s `createRequestOptions` (the options
@@ -58,30 +61,38 @@ function ensureCodexFetchPatched() {
58
61
  export const PROVIDERS = [
59
62
  { id: "openai-codex", label: "ChatGPT (OpenAI Codex)", auth: "chatgpt-oauth", env: [] },
60
63
  { id: "anthropic", label: "Anthropic", auth: "api_key", env: ["ANTHROPIC_API_KEY"] },
64
+ // #claude-subscription: Claude Pro/Max OAuth sign-in; requests run through the official
65
+ // Claude Code binary (anthropic-subscription.mjs), never against the API with a raw token.
66
+ { id: "anthropic-subscription", label: "Claude (Anthropic)", auth: "oauth", env: [] },
61
67
  { id: "openai", label: "OpenAI", auth: "api_key", env: ["OPENAI_API_KEY"] },
62
68
  { id: "google", label: "Google Gemini", auth: "api_key", env: ["GEMINI_API_KEY", "GOOGLE_API_KEY"] },
69
+ { id: "minimax", label: "MiniMax", auth: "api_key", env: ["MINIMAX_API_KEY"] }, { id: "minimax-cn", label: "MiniMax (China)", auth: "api_key", env: ["MINIMAX_CN_API_KEY"] },
63
70
  { id: "openrouter", label: "OpenRouter", auth: "api_key", env: ["OPENROUTER_API_KEY"] },
64
71
  { id: "cliproxy", label: "CLIProxyAPI", auth: "api_key", env: ["CLIPROXY_API_KEY"] },
72
+ // #885: LM Studio, Ollama or any OpenAI-compatible server; its address rides the stored credential
73
+ { id: OPENAI_COMPATIBLE_ID, label: "OpenAI-compatible server", auth: "api_key", env: [] },
65
74
  ];
66
75
 
67
76
  const factories = {
68
- "openai-codex": "openaiCodexProvider",
69
- anthropic: "anthropicProvider",
70
- openai: "openaiProvider",
71
- google: "googleProvider",
77
+ "openai-codex": "openaiCodexProvider", openai: "openaiProvider", google: "googleProvider", minimax: "minimaxProvider", "minimax-cn": "minimaxCnProvider",
72
78
  openrouter: "openrouterProvider",
73
79
  };
74
80
 
75
81
  const providerConfig = (id) => PROVIDERS.find((provider) => provider.id === id);
76
82
 
77
- export async function loadProvider(id, options = {}) {
78
- const { baseUrl, env = process.env } = options;
79
- const config = providerConfig(id);
80
- if (!config) throw Object.assign(new Error(`Unknown provider: ${id}`), { code: "UNKNOWN_PROVIDER" });
83
+ export async function loadProvider(id, { baseUrl, env = process.env } = {}) {
84
+ if (!providerConfig(id)) throw Object.assign(new Error(`Unknown provider: ${id}`), { code: "UNKNOWN_PROVIDER" });
81
85
  if (id === "openai-codex") ensureCodexFetchPatched();
86
+ // Both Anthropic lanes come from CozyClay's own module: the subscription lane, and pi-ai's
87
+ // API-key lane minus the OAuth login it also advertises (see anthropicLaneProvider).
82
88
  const provider = id === "cliproxy"
83
89
  ? await cliproxyProvider({ baseUrl, env })
84
- : await import(`@earendil-works/pi-ai/providers/${id}`).then((module) => module[factories[id]]());
90
+ : id === OPENAI_COMPATIBLE_ID ? await openaiCompatibleProvider() : id === "anthropic" || id === "anthropic-subscription"
91
+ ? await import("./anthropic-subscription.mjs").then((module) => module.anthropicLaneProvider(id))
92
+ : await import(`@earendil-works/pi-ai/providers/${id}`).then((module) => module[factories[id]]());
93
+ // pi 0.99 gave the API-key OpenAI lane a ChatGPT sign-in too; CozyClay's ChatGPT sign-in is the
94
+ // openai-codex lane, so the panel keeps showing one ChatGPT button.
95
+ if (id === "openai") provider.auth = { ...provider.auth, oauth: undefined };
85
96
  if (baseUrl && id !== "cliproxy") {
86
97
  provider.baseUrl = baseUrl;
87
98
  const getModels = provider.getModels.bind(provider);
@@ -95,19 +106,18 @@ const piEffort = (level) => level === "none" ? "off" : level === "ultra" ? "max"
95
106
  const wireEffort = (level) => (level === "off" ? "none" : level);
96
107
 
97
108
  const registryCatalogueState = new WeakMap();
98
-
99
109
  function catalogueState(models) {
100
110
  let state = registryCatalogueState.get(models);
101
111
  if (!state) {
102
- state = { fetch: null, models: [], extendedProviders: new WeakSet(), cliproxyFetch: null };
112
+ state = { fetch: null, models: [], extendedProviders: new WeakSet(), cliproxyFetch: null, cliproxyFetchError: null };
103
113
  registryCatalogueState.set(models, state);
104
114
  }
105
115
  return state;
106
116
  }
107
-
108
117
  function livePiModel(model, template) {
109
118
  const supported = new Set(model.efforts.map(piEffort));
110
- const thinkingLevelMap = Object.fromEntries(PI_EFFORT_LEVELS.map((level) => [level, supported.has(level) ? level : null]));
119
+ // map values are what goes on the wire: pi sends a model's off mapping, and Codex calls it "none"
120
+ const thinkingLevelMap = Object.fromEntries(PI_EFFORT_LEVELS.map((level) => [level, supported.has(level) ? wireEffort(level) : null]));
111
121
  return { ...template, id: model.id, name: model.label, thinkingLevelMap };
112
122
  }
113
123
 
@@ -171,6 +181,8 @@ export async function resolveModel(requested, options = {}) {
171
181
  if (!modelId) throw unknownModel(requested);
172
182
  const models = options.models ?? await createModels(options);
173
183
  if (!providerConfig(provider) && !models.getProvider?.(provider)) throw unknownModel(requested);
184
+ // a custom server's models are its live /v1/models list, read again when a turn names one not listed yet (#885)
185
+ if (provider === OPENAI_COMPATIBLE_ID && !models.getModel(provider, modelId)) await refreshOpenAICompatible(models);
174
186
  const model = models.getModel(provider, modelId);
175
187
  if (!model) throw unknownModel(requested);
176
188
  return { provider, modelId, model };
@@ -207,7 +219,8 @@ function resolveAuthSource(provider, { auth, saved, env }) {
207
219
  }
208
220
 
209
221
  export function providerStatus({ auth = defaultAuth, keys = defaultKeys, env = process.env } = {}) {
210
- const saved = keys.readKeys();
222
+ // a stored API key (keys file) and an OAuth sign-in such as a subscription (auth.json) both count
223
+ const saved = { ...keys.readKeys(), ...readCredentials() };
211
224
  return PROVIDERS.map((provider) => {
212
225
  const authSource = resolveAuthSource(provider, { auth, saved, env });
213
226
  return { id: provider.id, label: provider.label, authSource, signedIn: !!authSource };
@@ -254,119 +267,6 @@ function liveModelsCodex(result) {
254
267
  }).filter((model) => model.id);
255
268
  }
256
269
 
257
- // --- model roles (#717) ------------------------------------------------------
258
- //
259
- // One picked model (main) implies the rest of the lineup a turn may lean on: a
260
- // cheap helper, a mid-tier advisor, a model that can see images, and the
261
- // ordered alternatives to try when main fails. Subscription accounts usually
262
- // have exactly one signed-in provider, so every derived role stays on main's
263
- // provider; only the fallback chain may cross to another signed-in provider
264
- // that serves the very same model id. Both functions are pure over a
265
- // listAgentModels-shaped `{ providers }` catalog and never throw.
266
-
267
- export const MODEL_ROLES = ["main", "helper", "vision", "advisor"];
268
-
269
- const CLAUDE_TIERS = ["opus", "sonnet", "haiku"];
270
- const GPT_MINI_CLASS = /mini|nano/;
271
- const newestFirst = (a, b) => b.id.localeCompare(a.id, undefined, { numeric: true });
272
-
273
- function splitModelKey(key) {
274
- const slash = typeof key === "string" ? key.indexOf("/") : -1;
275
- return slash > 0 ? { providerId: key.slice(0, slash), modelId: key.slice(slash + 1) } : null;
276
- }
277
-
278
- const modelFamily = (id) => /^claude-/.test(id) ? "claude" : /^gpt-/.test(id) ? "gpt" : null;
279
- const claudeTier = (id) => CLAUDE_TIERS.find((tier) => id.startsWith(`claude-${tier}`)) ?? null;
280
- const takesImages = (model) => Array.isArray(model?.input) && model.input.includes("image");
281
- const modelKeyOf = (providerId, model) => model.key ?? `${providerId}/${model.id}`;
282
-
283
- /** main's provider entry, main's own model entry and the same-family models on that provider. */
284
- function locateMain(mainKey, catalog) {
285
- const parts = splitModelKey(mainKey);
286
- const providers = Array.isArray(catalog?.providers) ? catalog.providers : [];
287
- const provider = parts && providers.find((entry) => entry?.id === parts.providerId);
288
- const models = Array.isArray(provider?.models) ? provider.models.filter((model) => typeof model?.id === "string") : [];
289
- const main = parts && models.find((model) => model.id === parts.modelId);
290
- if (!main) return null;
291
- const family = modelFamily(main.id);
292
- return { providers, provider, models, main, family, sameFamily: family ? models.filter((model) => modelFamily(model.id) === family) : [] };
293
- }
294
-
295
- /** The first preference that matches a model: an exact id, or the newest id a pattern matches. */
296
- function pickModel(models, preferences) {
297
- for (const preference of preferences) {
298
- const match = typeof preference === "string"
299
- ? models.find((model) => model.id === preference)
300
- : [...models].filter((model) => preference.test(model.id)).sort(newestFirst)[0];
301
- if (match) return match;
302
- }
303
- return null;
304
- }
305
-
306
- /** One tier of a Claude lineup, preferring main's own version (opus-5-5 -> sonnet-5-5). */
307
- function claudeTierModel(models, mainId, tier) {
308
- const current = claudeTier(mainId);
309
- return pickModel(models, [current ? mainId.replace(`claude-${current}`, `claude-${tier}`) : null, new RegExp(`^claude-${tier}`)].filter(Boolean));
310
- }
311
-
312
- export function resolveRoleModels(mainKey, catalog) {
313
- const main = typeof mainKey === "string" ? mainKey : "";
314
- const found = locateMain(main, catalog);
315
- if (!found) return { main, helper: main, vision: main, advisor: main };
316
- const { provider, models, main: model, family, sameFamily } = found;
317
- const key = (entry) => (entry ? modelKeyOf(provider.id, entry) : main);
318
- const helper = family === "claude" ? pickModel(sameFamily, ["claude-haiku-5-5", /^claude-haiku/, /^claude-sonnet/])
319
- : family === "gpt" ? pickModel(sameFamily, [GPT_MINI_CLASS, /luna/])
320
- : null;
321
- const advisor = family === "claude" ? pickModel(sameFamily, ["claude-sonnet-5-5", /^claude-sonnet/])
322
- : family === "gpt" ? pickModel(sameFamily, ["gpt-6-astra"])
323
- : null;
324
- const vision = takesImages(model) ? model : models.find(takesImages);
325
- return { main, helper: key(helper), vision: key(vision), advisor: key(advisor) };
326
- }
327
-
328
- export function fallbackChain(mainKey, catalog) {
329
- const found = locateMain(mainKey, catalog);
330
- if (!found) return [];
331
- const { providers, provider, main, family, sameFamily } = found;
332
- const chain = [];
333
- // The same model served by another signed-in provider is the closest substitute there is.
334
- for (const other of providers) {
335
- if (other === provider || !other?.signedIn || !Array.isArray(other.models)) continue;
336
- const same = other.models.find((model) => model?.id === main.id);
337
- if (same) chain.push(modelKeyOf(other.id, same));
338
- }
339
- // Then a step down main's own family on main's provider.
340
- if (family === "claude") {
341
- const tier = CLAUDE_TIERS.indexOf(claudeTier(main.id));
342
- if (tier !== -1) {
343
- for (const lower of CLAUDE_TIERS.slice(tier + 1)) {
344
- const model = claudeTierModel(sameFamily, main.id, lower);
345
- if (model) chain.push(modelKeyOf(provider.id, model));
346
- }
347
- }
348
- } else if (family === "gpt" && !GPT_MINI_CLASS.test(main.id)) {
349
- for (const model of [pickModel(sameFamily, ["gpt-6-astra"]), pickModel(sameFamily, [GPT_MINI_CLASS])]) {
350
- if (model) chain.push(modelKeyOf(provider.id, model));
351
- }
352
- }
353
- const mainKeyFound = modelKeyOf(provider.id, main);
354
- return [...new Set(chain)].filter((key) => key !== mainKeyFound).slice(0, 3);
355
- }
356
-
357
- /** `roles.byMain` for listAgentModels: every model of every signed-in provider. */
358
- function rolesByMain(catalog) {
359
- const byMain = {};
360
- for (const provider of catalog.providers) {
361
- if (!provider.signedIn) continue;
362
- for (const model of provider.models) {
363
- const { helper, vision, advisor } = resolveRoleModels(model.key, catalog);
364
- byMain[model.key] = { helper, vision, advisor, fallback: fallbackChain(model.key, catalog) };
365
- }
366
- }
367
- return { byMain };
368
- }
369
-
370
270
  const astraFirst = (a, b) => Number(b.id === "gpt-6-astra") - Number(a.id === "gpt-6-astra");
371
271
  const cliproxyFirst = (a, b) => astraFirst(a, b) || Number(b.id === "claude-fable-5-1") - Number(a.id === "claude-fable-5-1");
372
272
 
@@ -379,7 +279,8 @@ const cliproxyFirst = (a, b) => astraFirst(a, b) || Number(b.id === "claude-fabl
379
279
  export async function listAgentModels({ models, codex, auth = defaultAuth, keys = defaultKeys, env = process.env } = {}) {
380
280
  const { getSupportedThinkingLevels } = await import("@earendil-works/pi-ai");
381
281
  const resolvedModels = models ?? await createModels({ auth, keys, env });
382
- const saved = keys.readKeys();
282
+ // a stored API key (keys file) and an OAuth sign-in such as a subscription (auth.json) both count
283
+ const saved = { ...keys.readKeys(), ...readCredentials() };
383
284
  const providers = [];
384
285
  for (const provider of PROVIDERS) {
385
286
  const authResult = await resolvedModels.getAuth(provider.id).catch(() => undefined);
@@ -408,14 +309,18 @@ export async function listAgentModels({ models, codex, auth = defaultAuth, keys
408
309
  }
409
310
  }
410
311
  list = [...list].sort(cliproxyFirst);
312
+ const error = catalogueState(resolvedModels).cliproxyFetchError; providers.push({ id: provider.id, label: provider.label, signedIn, authSource, models: list, ...(error ? { error } : {}) }); continue;
313
+ } else if (provider.id === OPENAI_COMPATIBLE_ID && signedIn) {
314
+ // the server's own /v1/models is the lineup, read on every listing so a model loaded since then shows up
315
+ const error = await refreshOpenAICompatible(resolvedModels);
316
+ providers.push({ id: provider.id, label: provider.label, signedIn, authSource, models: catalogModels(resolvedModels, provider.id, getSupportedThinkingLevels), ...(error ? { error } : {}) }); continue;
411
317
  }
412
318
  providers.push({ id: provider.id, label: provider.label, signedIn, authSource, models: list });
413
319
  }
414
320
  // a provider outside the curated list joins the picker once a pi login stored its credential here (#pi-login); ambient
415
321
  // credentials (an AWS profile, cloud ADC files) alone do not list it, so the picker shows what the author connected
416
- const stored = new Set([...Object.keys(readCredentials()), ...Object.keys(saved)]);
417
322
  for (const provider of resolvedModels.getProviders?.() ?? []) {
418
- const check = providerConfig(provider.id) || !stored.has(provider.id) ? undefined : await resolvedModels.checkAuth?.(provider.id).catch(() => undefined);
323
+ const check = providerConfig(provider.id) || !(provider.id in saved) ? undefined : await resolvedModels.checkAuth?.(provider.id).catch(() => undefined);
419
324
  if (check) providers.push({ id: provider.id, label: provider.name ?? provider.id, signedIn: true, authSource: check.type === "oauth" || /stored/i.test(check.source ?? "") ? "file" : "env", models: catalogModels(resolvedModels, provider.id, getSupportedThinkingLevels) });
420
325
  }
421
326
  // The flat union is what the panel's dropdown reads today (`models[].id`);
@@ -34,15 +34,15 @@
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-C4QTcDcb.js"></script>
37
+ <script type="module" crossorigin src="/assets/app-DwETmoQZ.js"></script>
38
38
  <link rel="modulepreload" crossorigin href="/assets/rolldown-runtime-hePW80VL.js">
39
39
  <link rel="modulepreload" crossorigin href="/assets/modulepreload-polyfill-P2Xu9kJm.js">
40
- <link rel="modulepreload" crossorigin href="/assets/task-board-BqReHzOi.js">
40
+ <link rel="modulepreload" crossorigin href="/assets/task-board-DFfNa0xg.js">
41
41
  <link rel="modulepreload" crossorigin href="/assets/fflate.module-KbGj5-H6.js">
42
- <link rel="modulepreload" crossorigin href="/assets/scene-asset-sync-qoxcdrhj.js">
43
- <link rel="modulepreload" crossorigin href="/assets/motion3-compile-dgGhrvPK.js">
42
+ <link rel="modulepreload" crossorigin href="/assets/scene-asset-sync-Bz59Ghdz.js">
43
+ <link rel="modulepreload" crossorigin href="/assets/motion3-compile-DDG3wklD.js">
44
44
  <link rel="stylesheet" crossorigin href="/assets/scene-asset-sync-BEjjhNEo.css">
45
- <link rel="stylesheet" crossorigin href="/assets/app-Bhe0TpGz.css">
45
+ <link rel="stylesheet" crossorigin href="/assets/app-BdRlMf4J.css">
46
46
  </head>
47
47
  <body>
48
48
  <div id="root">
@@ -0,0 +1 @@
1
+ import{r as e}from"./rolldown-runtime-hePW80VL.js";import{Ti as t,c as n,d as r,f as i,fa as a,l as o,m as s,o as c,v as l}from"./task-board-DFfNa0xg.js";import{o as u,r as d,t as f}from"./motion3-compile-DDG3wklD.js";var p=e(a(),1),m=2e3,h=(e,t)=>new Promise(n=>{let r=setTimeout(n,e);t.addEventListener(`abort`,()=>{clearTimeout(r),n()},{once:!0})});function g(e,n){(0,p.useEffect)(()=>{let r=new Map;for(let[t,i]of Object.entries(e))for(let e of i)r.set(e,n[t]??t);let i=()=>{for(let e of document.querySelectorAll(`[data-node-id]`)){let n=r.get(e.getAttribute(`data-node-id`));n?(e.setAttribute(`data-task-scope`,n),e.setAttribute(`title`,t(`In task: ${n}`,`작업 중: ${n}`,`任务中:${n}`))):e.hasAttribute(`data-task-scope`)&&(e.removeAttribute(`data-task-scope`),e.removeAttribute(`title`))}};i();let a=new MutationObserver(i);return a.observe(document.body,{childList:!0,subtree:!0}),()=>{a.disconnect(),r.clear(),i()}},[e,n])}function _({sessionId:e,pulse:t,labelOf:a,onMainNote:_}){let v=(0,p.useMemo)(()=>c(),[]),y=u(),[b,x]=(0,p.useReducer)((e,t)=>t.seed?s(e,t.seed):t.reset?n():r(e,t.event),void 0,n),[S,C]=(0,p.useState)({}),[w,T]=(0,p.useState)(f),E=(0,p.useRef)(0),D=(0,p.useRef)(_);(0,p.useEffect)(()=>{E.current=b.after,D.current=_},[b.after,_]),(0,p.useEffect)(()=>{x({reset:!0}),E.current=0},[e]),(0,p.useEffect)(()=>d(T),[]),(0,p.useEffect)(()=>y.subscribe(e=>{typeof e?.type!=`string`||!e.type.startsWith(`task.`)||(C(t=>i(t,e)),(e.type===`task.userEdit`||e.type===`task.undone`)&&x({event:e}))}),[y]),(0,p.useEffect)(()=>{if(!e)return;let t=new AbortController;return(async()=>{for(;!t.signal.aborted;){try{let{tasks:n}=await v.board(e);x({seed:n}),await v.events(e,E.current,e=>{if(e.type===`main.turn`&&e.turnId){let n=``;v.frames(e.turnId,e=>{e.type===`text.delta`&&(n+=e.text??``),e.type===`tool.start`&&(n=``)},t.signal).then(()=>D.current?.(n),()=>{})}x({event:e})},t.signal)}catch{}await h(m,t.signal)}})(),()=>t.abort()},[v,e,t]);let O=b.order.filter(e=>b.tasks[e]?.status===`running`&&b.tasks[e]?.turnId),k=O.map(e=>`${e}:${b.tasks[e].turnId}`).join();return(0,p.useEffect)(()=>{let e=O.map(e=>{let t=new AbortController,{turnId:n}=b.tasks[e];return v.frames(n,t=>{let n=o(t,a);n&&x({event:{type:`task.progress`,taskId:e,status:`running`,progress:n}})},t.signal).catch(()=>{}),t});return()=>e.forEach(e=>e.abort())},[v,k,a]),g(S,(0,p.useMemo)(()=>Object.fromEntries(b.order.map(e=>[e,b.tasks[e].title])),[b])),(0,p.useEffect)(()=>{l.set({client:v,labelOf:a,undo:async e=>{let t=await y.run(`task.undo`,{taskId:e});if(t?.ok===!1)throw Error(t.message??t.code);x({event:{type:`task.undone`,taskId:e}})}})},[y,v,a]),(0,p.useEffect)(()=>{l.set({board:b,activity:w})},[b,w]),(0,p.useEffect)(()=>()=>l.set({board:n(),open:null}),[e]),null}export{_ as default};