@camstack/addon-training 0.1.21 → 0.1.23

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 (43) hide show
  1. package/dist/MaskShapeCanvas-BokARvOj.mjs +13806 -0
  2. package/dist/MotionZonesSettings-CwGjtvsX.mjs +310 -0
  3. package/dist/{PrivacyMaskSettings-Bo1S23wt.mjs → PrivacyMaskSettings-CM_WsrKB.mjs} +70 -45
  4. package/dist/{SceneMonitorEditor-DnQJ7Hx3.mjs → SceneMonitorEditor-B-sAJ--C.mjs} +311 -311
  5. package/dist/_stub.js +7372 -16803
  6. package/dist/{_virtual_mf-localSharedImportMap___mfe_internal__addon_training_page-DQi_LrZZ.mjs → _virtual_mf-localSharedImportMap___mfe_internal__addon_training_page-DKblQaAP.mjs} +2 -2
  7. package/dist/_virtual_mf___mfe_internal__addon_training_page__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-ff7X-Xlf.mjs +26 -0
  8. package/dist/_virtual_mf___mfe_internal__addon_training_page__loadShare___mf_0_tanstack_mf_1_react_mf_2_query__loadShare__.js-BSOdwqcC.mjs +26 -0
  9. package/dist/_virtual_mf___mfe_internal__addon_training_page__loadShare___mf_0_trpc_mf_1_client__loadShare__.js-B3rryujf.mjs +25 -0
  10. package/dist/_virtual_mf___mfe_internal__addon_training_page__loadShare___mf_0_trpc_mf_1_react_mf_2_query__loadShare__.js-nL4MGFRP.mjs +26 -0
  11. package/dist/{_virtual_mf___mfe_internal__addon_training_page__loadShare__react__loadShare__.js-DeDbF7NI.mjs → _virtual_mf___mfe_internal__addon_training_page__loadShare__react__loadShare__.js-BT_wL8s3.mjs} +1 -1
  12. package/dist/_virtual_mf___mfe_internal__addon_training_page__loadShare__react_mf_1_jsx_mf_2_runtime__loadShare__.js-DDBEvLdo.mjs +26 -0
  13. package/dist/_virtual_mf___mfe_internal__addon_training_page__loadShare__react_mf_2_dom__loadShare__.js-CK-LlgOt.mjs +26 -0
  14. package/dist/_virtual_mf___mfe_internal__addon_training_page__loadShare__react_mf_2_dom_mf_1_client__loadShare__.js-gTIQrhKs.mjs +25 -0
  15. package/dist/{dist-BSYBh1ZO.mjs → dist-CVDWIKl7.mjs} +52 -2
  16. package/dist/{dist-BCLkrWJc.js → dist-DwF1P1v7.js} +87 -1
  17. package/dist/{hostInit-Bx2a8M7w.mjs → hostInit-Di0tDODX.mjs} +1 -1
  18. package/dist/index.js +8 -1
  19. package/dist/index.mjs +7 -0
  20. package/dist/orchestrator/training.addon.js +8 -1
  21. package/dist/orchestrator/training.addon.mjs +8 -1
  22. package/dist/{player-overlays-BR2_H0wZ.mjs → player-overlays-BFAqW2L1.mjs} +10 -32
  23. package/dist/providers/modal/training-modal.addon.js +196 -123
  24. package/dist/providers/modal/training-modal.addon.mjs +196 -123
  25. package/dist/remoteEntry.js +1 -1
  26. package/dist/{responsive-emJo3EdF.mjs → responsive-BBlQWFFi.mjs} +38 -42
  27. package/dist/{scene-monitor-copy-CcQxbRXa.mjs → scene-monitor-copy-D5mKGqt0.mjs} +4 -4
  28. package/dist/{square-CErp1WgN.mjs → square-D0FYSm8b.mjs} +4 -29
  29. package/dist/{trash-2-Drz0fMgI.mjs → trash-2-DdjGvgDw.mjs} +1 -1
  30. package/dist/{virtual_mf-REMOTE_ENTRY_ID___mfe_internal__addon_training_page__remoteEntry_js-C-uqIKLg.mjs → virtual_mf-REMOTE_ENTRY_ID___mfe_internal__addon_training_page__remoteEntry_js-Dw400Y0a.mjs} +390 -399
  31. package/package.json +1 -1
  32. package/dist/MaskShapeCanvas-Kfg9dl6B.mjs +0 -686
  33. package/dist/MotionZonesSettings-DflIL9ss.mjs +0 -292
  34. package/dist/ReactKonva-DGuYUN0L.mjs +0 -5416
  35. package/dist/_virtual_mf___mfe_internal__addon_training_page__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-C7dks3FG.mjs +0 -26
  36. package/dist/_virtual_mf___mfe_internal__addon_training_page__loadShare___mf_0_tanstack_mf_1_react_mf_2_query__loadShare__.js-DpjirGmB.mjs +0 -89
  37. package/dist/_virtual_mf___mfe_internal__addon_training_page__loadShare___mf_0_trpc_mf_1_client__loadShare__.js-CFCVuRAs.mjs +0 -58
  38. package/dist/_virtual_mf___mfe_internal__addon_training_page__loadShare___mf_0_trpc_mf_1_react_mf_2_query__loadShare__.js-BkvDudqY.mjs +0 -62
  39. package/dist/_virtual_mf___mfe_internal__addon_training_page__loadShare__react_mf_1_jsx_mf_2_runtime__loadShare__.js-DnrbBvrw.mjs +0 -34
  40. package/dist/_virtual_mf___mfe_internal__addon_training_page__loadShare__react_mf_2_dom__loadShare__.js-DAZkHxxU.mjs +0 -45
  41. package/dist/_virtual_mf___mfe_internal__addon_training_page__loadShare__react_mf_2_dom_mf_1_client__loadShare__.js-CHqN9NLt.mjs +0 -34
  42. package/dist/grid-2x2-D9syAI6z.mjs +0 -21
  43. package/dist/lib-CVGFTwe0.mjs +0 -7770
@@ -1,4 +1,4 @@
1
- import { a as addonPagesSourceCapability, i as TrainingResultSchema, l as BaseAddon, n as TrainingJobSchema, o as parseTrainingEventLine, r as TrainingJobSpecSchema, s as trainingCapability, t as TRAINING_JOB_SCHEMA } from "../dist-BSYBh1ZO.mjs";
1
+ import { a as addonPagesSourceCapability, i as TrainingResultSchema, l as BaseAddon, n as TrainingJobSchema, o as parseTrainingEventLine, r as TrainingJobSpecSchema, s as trainingCapability, t as TRAINING_JOB_SCHEMA } from "../dist-CVDWIKl7.mjs";
2
2
  import path from "node:path";
3
3
  import { createHash } from "node:crypto";
4
4
  import { createReadStream } from "node:fs";
@@ -30,6 +30,13 @@ function providerPort(api) {
30
30
  addonId
31
31
  });
32
32
  },
33
+ updateEntry: async (i) => {
34
+ const addonId = await addonForEntry(i.entryId);
35
+ return p.updateEntry.mutate({
36
+ ...i,
37
+ addonId
38
+ });
39
+ },
33
40
  deleteEntry: async (i) => {
34
41
  const addonId = await addonForEntry(i.entryId);
35
42
  return p.deleteEntry.mutate({
@@ -1,10 +1,10 @@
1
- import { S as e, _ as t, l as n, u as r } from "./_virtual_mf___mfe_internal__addon_training_page__loadShare__react__loadShare__.js-DeDbF7NI.mjs";
2
- import { b as i, y as a } from "./responsive-emJo3EdF.mjs";
3
- import { n as o, r as s, t as c } from "./_virtual_mf___mfe_internal__addon_training_page__loadShare__react_mf_1_jsx_mf_2_runtime__loadShare__.js-DnrbBvrw.mjs";
4
- var l = i("chevron-down", [["path", {
1
+ import { b as e, g as t, l as n, u as r } from "./_virtual_mf___mfe_internal__addon_training_page__loadShare__react__loadShare__.js-BT_wL8s3.mjs";
2
+ import { v as i, y as a } from "./responsive-BBlQWFFi.mjs";
3
+ import { n as o, r as s, t as c } from "./_virtual_mf___mfe_internal__addon_training_page__loadShare__react_mf_1_jsx_mf_2_runtime__loadShare__.js-DDBEvLdo.mjs";
4
+ var l = a("chevron-down", [["path", {
5
5
  d: "m6 9 6 6 6-6",
6
6
  key: "qrunsl"
7
- }]]), u = i("chevron-right", [["path", {
7
+ }]]), u = a("chevron-right", [["path", {
8
8
  d: "m9 18 6-6-6-6",
9
9
  key: "mthhwq"
10
10
  }]]);
@@ -32,35 +32,13 @@ function d({ title: e, icon: n, headerRight: r, collapsible: i = !1, defaultOpen
32
32
  }) : null]
33
33
  });
34
34
  }
35
- function f({ label: e, icon: t, children: n, footer: r }) {
36
- return /* @__PURE__ */ s("div", {
37
- className: "min-w-0 rounded-lg border border-border bg-surface p-2 sm:p-2.5",
38
- children: [
39
- /* @__PURE__ */ s("div", {
40
- className: "mb-0.5 flex min-w-0 items-center gap-1.5 text-[10px] uppercase tracking-wider text-foreground-subtle",
41
- children: [t, /* @__PURE__ */ o("span", {
42
- className: "min-w-0 truncate",
43
- children: e
44
- })]
45
- }),
46
- /* @__PURE__ */ o("div", {
47
- className: "truncate text-lg font-semibold leading-tight text-foreground",
48
- children: n
49
- }),
50
- r == null ? null : /* @__PURE__ */ o("div", {
51
- className: "mt-0.5 truncate text-[10px] text-foreground-subtle",
52
- children: r
53
- })
54
- ]
55
- });
56
- }
57
- e(), a("camstack:player-overlays-state", null);
58
- var p = a("camstack:player-overlays-actions", null);
59
- function m(e) {
60
- let t = n(p);
35
+ e(), i("camstack:player-overlays-state", null);
36
+ var f = i("camstack:player-overlays-actions", null);
37
+ function p(e) {
38
+ let t = n(f);
61
39
  r(() => {
62
40
  if (!(!t || !e)) return t.setLayer(e), () => t.removeLayer(e.id);
63
41
  }, [t, e]);
64
42
  }
65
43
  //#endregion
66
- export { l as a, u as i, f as n, d as r, m as t };
44
+ export { l as i, d as n, u as r, p as t };
@@ -1,5 +1,5 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
- const require_dist = require("../../dist-BCLkrWJc.js");
2
+ const require_dist = require("../../dist-DwF1P1v7.js");
3
3
  let node_path = require("node:path");
4
4
  node_path = require_dist.__toESM(node_path, 1);
5
5
  let node_crypto = require("node:crypto");
@@ -11,14 +11,43 @@ let timers_promises = require("timers/promises");
11
11
  let node_console = require("node:console");
12
12
  let node_os = require("node:os");
13
13
  let node_stream = require("node:stream");
14
- //#region src/providers/modal/modal-entries.ts
14
+ //#region src/providers/modal/entry-secret-view.ts
15
15
  /**
16
- * Modal provider entries — one credentials + defaults row per operator
17
- * account. Pure helpers so the store is unit-tested without BaseAddon.
16
+ * Build the {@link SecretFieldView} the Providers edit form needs for an
17
+ * entry's token secret.
18
18
  *
19
- * A pre-multi-entry config (flat tokenId/tokenSecret on the addon) migrates
20
- * to a single entry named {@link DEFAULT_MODAL_ENTRY_NAME}.
19
+ * `readStoredGlobalKey` / `this.config` answer VALUES (D798), never markers —
20
+ * so `toSecretFieldView(plaintext)` alone yields an inline view (`$secret:
21
+ * null`) and `SecretFieldControl` cannot reveal. When the secret is set we
22
+ * prefer the deterministic keychain ref (`ctx.secrets.ref`) so the eye can
23
+ * call `keychain-admin.reveal` (D789).
21
24
  */
25
+ /** View for one entry row, or `null` when nothing is stored. */
26
+ function entryTokenSecretView(tokenSecret, secrets, entryId) {
27
+ if (require_dist.isSecretMarker(tokenSecret)) return require_dist.toSecretFieldView(tokenSecret);
28
+ const plain = typeof tokenSecret === "string" ? tokenSecret.trim() : "";
29
+ if (plain === "") return null;
30
+ if (secrets === void 0) return require_dist.toSecretFieldView(plain);
31
+ return {
32
+ $secret: secrets.ref(require_dist.globalSecretScope(), require_dist.arrayItemFieldPath("entries", entryId, "tokenSecret")),
33
+ v: 1,
34
+ set: true
35
+ };
36
+ }
37
+ /** View for the legacy flat `tokenSecret` (pre-multi-entry Default). */
38
+ function flatTokenSecretView(tokenSecret, secrets) {
39
+ if (require_dist.isSecretMarker(tokenSecret)) return require_dist.toSecretFieldView(tokenSecret);
40
+ const plain = typeof tokenSecret === "string" ? tokenSecret.trim() : "";
41
+ if (plain === "") return null;
42
+ if (secrets === void 0) return require_dist.toSecretFieldView(plain);
43
+ return {
44
+ $secret: secrets.ref(require_dist.globalSecretScope(), "tokenSecret"),
45
+ v: 1,
46
+ set: true
47
+ };
48
+ }
49
+ //#endregion
50
+ //#region src/providers/modal/modal-entries.ts
22
51
  var MODAL_TYPE_ID = "modal";
23
52
  var DEFAULT_MODAL_ENTRY_NAME = "Default";
24
53
  var MODAL_ICON_ASSET = "assets/training-modal/icon.svg";
@@ -29,22 +58,44 @@ var DEFAULT_GRACE = 24;
29
58
  function legacyConfigured(config) {
30
59
  return (config.tokenId ?? "").trim() !== "" || (config.tokenSecret ?? "").trim() !== "";
31
60
  }
61
+ /**
62
+ * Keychain / secretStatePaths may omit an empty `tokenSecret` from a stored
63
+ * row (writing `''` is KEEP, not "store empty"). Readers must never see
64
+ * `undefined` and then call `.trim()`.
65
+ */
66
+ function secretAsString(value) {
67
+ return typeof value === "string" ? value : "";
68
+ }
69
+ function normalizeModalEntry(entry) {
70
+ return {
71
+ id: entry.id,
72
+ name: entry.name ?? "Default",
73
+ tokenId: entry.tokenId ?? "",
74
+ tokenSecret: secretAsString(entry.tokenSecret),
75
+ environment: entry.environment ?? "",
76
+ gpuType: entry.gpuType ?? DEFAULT_GPU,
77
+ region: entry.region ?? "",
78
+ costCapUsd: entry.costCapUsd ?? DEFAULT_COST,
79
+ maxRuntimeHours: entry.maxRuntimeHours ?? DEFAULT_HOURS,
80
+ failedDatasetGraceHours: entry.failedDatasetGraceHours ?? DEFAULT_GRACE
81
+ };
82
+ }
32
83
  /** Entries the operator sees — synthesises Default from legacy flat fields. */
33
84
  function effectiveModalEntries(config) {
34
- if (config.entries.length > 0) return config.entries;
85
+ if (config.entries.length > 0) return config.entries.map((e) => normalizeModalEntry(e));
35
86
  if (!legacyConfigured(config)) return [];
36
- return [{
87
+ return [normalizeModalEntry({
37
88
  id: "legacy-default",
38
89
  name: DEFAULT_MODAL_ENTRY_NAME,
39
- tokenId: config.tokenId ?? "",
40
- tokenSecret: config.tokenSecret ?? "",
41
- environment: config.environment ?? "",
42
- gpuType: config.gpuType ?? DEFAULT_GPU,
43
- region: config.region ?? "",
44
- costCapUsd: config.costCapUsd ?? DEFAULT_COST,
45
- maxRuntimeHours: config.maxRuntimeHours ?? DEFAULT_HOURS,
46
- failedDatasetGraceHours: config.failedDatasetGraceHours ?? DEFAULT_GRACE
47
- }];
90
+ tokenId: config.tokenId,
91
+ tokenSecret: config.tokenSecret,
92
+ environment: config.environment,
93
+ gpuType: config.gpuType,
94
+ region: config.region,
95
+ costCapUsd: config.costCapUsd,
96
+ maxRuntimeHours: config.maxRuntimeHours,
97
+ failedDatasetGraceHours: config.failedDatasetGraceHours
98
+ })];
48
99
  }
49
100
  function findModalEntry(config, entryId) {
50
101
  return effectiveModalEntries(config).find((e) => e.id === entryId) ?? null;
@@ -128,6 +179,43 @@ function deleteModalEntry(config, entryId) {
128
179
  }
129
180
  };
130
181
  }
182
+ function updateModalEntry(config, entryId, patch) {
183
+ const current = [...effectiveModalEntries(config)];
184
+ const index = current.findIndex((e) => e.id === entryId);
185
+ if (index < 0) return {
186
+ ok: false,
187
+ reason: "unknown-entry"
188
+ };
189
+ const prev = current[index];
190
+ const secret = patch.tokenSecret !== void 0 && patch.tokenSecret !== "" ? patch.tokenSecret : prev.tokenSecret;
191
+ current[index] = {
192
+ id: prev.id,
193
+ name: patch.name !== void 0 ? patch.name : prev.name,
194
+ tokenId: patch.tokenId !== void 0 ? patch.tokenId : prev.tokenId,
195
+ tokenSecret: secret,
196
+ environment: patch.environment !== void 0 ? patch.environment : prev.environment,
197
+ gpuType: patch.gpuType !== void 0 ? patch.gpuType : prev.gpuType,
198
+ region: patch.region !== void 0 ? patch.region : prev.region,
199
+ costCapUsd: patch.costCapUsd !== void 0 ? patch.costCapUsd : prev.costCapUsd,
200
+ maxRuntimeHours: patch.maxRuntimeHours !== void 0 ? patch.maxRuntimeHours : prev.maxRuntimeHours,
201
+ failedDatasetGraceHours: patch.failedDatasetGraceHours !== void 0 ? patch.failedDatasetGraceHours : prev.failedDatasetGraceHours
202
+ };
203
+ return {
204
+ ok: true,
205
+ entryId,
206
+ config: {
207
+ entries: current,
208
+ tokenId: "",
209
+ tokenSecret: "",
210
+ environment: config.environment ?? "",
211
+ gpuType: config.gpuType ?? DEFAULT_GPU,
212
+ region: config.region ?? "",
213
+ costCapUsd: config.costCapUsd ?? DEFAULT_COST,
214
+ maxRuntimeHours: config.maxRuntimeHours ?? DEFAULT_HOURS,
215
+ failedDatasetGraceHours: config.failedDatasetGraceHours ?? DEFAULT_GRACE
216
+ }
217
+ };
218
+ }
131
219
  /**
132
220
  * Mint a stable id for every persisted entry that lacks one. Operator-typed
133
221
  * empty ids (legacy settings UI) must never stay empty — createEntry always
@@ -344,7 +432,10 @@ var ModalTrainingProvider = class {
344
432
  constructor(deps) {
345
433
  this.deps = deps;
346
434
  }
347
- describe = async () => this.deps.listEntries().map((entry) => this.descriptorOf(entry));
435
+ describe = async () => {
436
+ const views = await this.deps.secretViews();
437
+ return this.deps.listEntries().map((entry) => this.descriptorOf(entry, views.get(entry.id) ?? null));
438
+ };
348
439
  describeTypes = async () => [{
349
440
  typeId: MODAL_TYPE_ID,
350
441
  label: "Modal",
@@ -359,6 +450,21 @@ var ModalTrainingProvider = class {
359
450
  configured: this.entryConfigured(entry)
360
451
  }));
361
452
  createEntry = async (input) => this.deps.createEntry(input);
453
+ updateEntry = async (input) => {
454
+ const secret = input.tokenSecret === void 0 ? {} : require_dist.isSecretClear(input.tokenSecret) ? { clearTokenSecret: true } : input.tokenSecret === "" ? {} : { tokenSecret: input.tokenSecret };
455
+ await this.deps.updateEntry(input.entryId, {
456
+ name: input.name,
457
+ ...input.tokenId !== void 0 ? { tokenId: input.tokenId } : {},
458
+ ...secret,
459
+ ...input.environment !== void 0 ? { environment: input.environment } : {},
460
+ ...input.gpuType !== void 0 ? { gpuType: input.gpuType } : {},
461
+ ...input.region !== void 0 ? { region: input.region } : {},
462
+ ...input.costCapUsd !== void 0 ? { costCapUsd: input.costCapUsd } : {},
463
+ ...input.maxRuntimeSec !== void 0 ? { maxRuntimeHours: input.maxRuntimeSec / 3600 } : {},
464
+ ...input.failedDatasetGraceHours !== void 0 ? { failedDatasetGraceHours: input.failedDatasetGraceHours } : {}
465
+ });
466
+ return { ok: true };
467
+ };
362
468
  deleteEntry = async (input) => {
363
469
  await this.deps.deleteEntry(input.entryId);
364
470
  return { ok: true };
@@ -636,9 +742,12 @@ var ModalTrainingProvider = class {
636
742
  }))];
637
743
  };
638
744
  entryConfigured(entry) {
639
- return entry.tokenId.trim() !== "" && entry.tokenSecret.trim() !== "";
745
+ const secret = typeof entry.tokenSecret === "string" ? entry.tokenSecret : "";
746
+ return (entry.tokenId ?? "").trim() !== "" && secret.trim() !== "";
640
747
  }
641
- descriptorOf(entry) {
748
+ descriptorOf(entry, tokenSecretView) {
749
+ const tokenId = entry.tokenId ?? "";
750
+ const environment = entry.environment ?? "";
642
751
  return {
643
752
  addonId: this.deps.addonId,
644
753
  entryId: entry.id,
@@ -655,12 +764,16 @@ var ModalTrainingProvider = class {
655
764
  dataResidencyNote: "Modal keeps the frames on a Volume of your workspace for the length of the job; the training container sees them only while it runs.",
656
765
  configured: this.entryConfigured(entry),
657
766
  defaults: {
658
- gpuType: entry.gpuType,
659
- region: entry.region,
660
- costCapUsd: entry.costCapUsd,
661
- maxRuntimeSec: Math.round(entry.maxRuntimeHours * 3600),
662
- failedDatasetGraceHours: entry.failedDatasetGraceHours
663
- }
767
+ gpuType: entry.gpuType ?? "L4",
768
+ region: entry.region ?? "",
769
+ costCapUsd: entry.costCapUsd ?? 5,
770
+ maxRuntimeSec: Math.round((entry.maxRuntimeHours ?? 4) * 3600),
771
+ failedDatasetGraceHours: entry.failedDatasetGraceHours ?? 24
772
+ },
773
+ tokenId,
774
+ environment,
775
+ hasTokenSecret: tokenSecretView !== null,
776
+ tokenSecretView
664
777
  };
665
778
  }
666
779
  requireGateway(entryId) {
@@ -88513,6 +88626,12 @@ async function fetchText(url) {
88513
88626
  var TrainingModalAddon = class extends require_dist.BaseAddon {
88514
88627
  ledgerState = null;
88515
88628
  gateways = /* @__PURE__ */ new Map();
88629
+ /**
88630
+ * Entry secrets live on `entries[id].tokenSecret` (and legacy flat
88631
+ * `tokenSecret`). Declared here — not via an editable-array form — so the
88632
+ * Training → Providers page is the only UI that edits entries.
88633
+ */
88634
+ secretStatePaths = ["entries[id].tokenSecret", "tokenSecret"];
88516
88635
  constructor() {
88517
88636
  super({
88518
88637
  entries: [],
@@ -88528,9 +88647,15 @@ var TrainingModalAddon = class extends require_dist.BaseAddon {
88528
88647
  }
88529
88648
  async onInitialize() {
88530
88649
  let next = this.config;
88531
- if (next.entries.length === 0) next = migrateLegacyModalConfig(next, () => (0, node_crypto.randomUUID)());
88650
+ let migratedLegacy = false;
88651
+ if (next.entries.length === 0) {
88652
+ const migrated = migrateLegacyModalConfig(next, () => (0, node_crypto.randomUUID)());
88653
+ migratedLegacy = migrated !== next;
88654
+ next = migrated;
88655
+ }
88532
88656
  next = ensureModalEntryIds(next, () => (0, node_crypto.randomUUID)());
88533
88657
  if (next !== this.config) await this.updateGlobalSettings(next);
88658
+ if (migratedLegacy) await this.clearSecrets(["tokenSecret"]);
88534
88659
  this.ledgerState = this.state(LEDGER_KEY, LedgerSchema, {});
88535
88660
  const peer = this.ctx.peerBytes;
88536
88661
  if (peer === void 0) throw new Error("training-modal needs the peer-bytes facility");
@@ -88545,12 +88670,28 @@ var TrainingModalAddon = class extends require_dist.BaseAddon {
88545
88670
  await this.updateGlobalSettings(result.config);
88546
88671
  return { entryId: result.entryId };
88547
88672
  },
88673
+ updateEntry: async (entryId, patch) => {
88674
+ const { clearTokenSecret, ...rest } = patch;
88675
+ const result = updateModalEntry(this.config, entryId, rest);
88676
+ if (!result.ok) throw new Error(`cannot update entry: ${result.reason}`);
88677
+ if (clearTokenSecret === true) await this.updateGlobalSettings({
88678
+ ...result.config,
88679
+ entries: result.config.entries.map((entry) => entry.id === entryId ? {
88680
+ ...entry,
88681
+ tokenSecret: require_dist.SECRET_CLEAR
88682
+ } : entry)
88683
+ });
88684
+ else await this.updateGlobalSettings(result.config);
88685
+ this.closeGateway(entryId);
88686
+ },
88548
88687
  deleteEntry: async (entryId) => {
88549
88688
  const result = deleteModalEntry(this.config, entryId);
88550
88689
  if (!result.ok) throw new Error(`cannot delete entry: ${result.reason}`);
88551
88690
  await this.updateGlobalSettings(result.config);
88691
+ await this.clearSecrets(["tokenSecret"]);
88552
88692
  this.closeGateway(entryId);
88553
88693
  },
88694
+ secretViews: () => this.readEntrySecretViews(),
88554
88695
  ledger: this.ledger(),
88555
88696
  peer,
88556
88697
  fetchText,
@@ -88559,7 +88700,10 @@ var TrainingModalAddon = class extends require_dist.BaseAddon {
88559
88700
  });
88560
88701
  this.ctx.logger.info("Modal training provider ready", { meta: {
88561
88702
  entries: effectiveModalEntries(this.config).length,
88562
- configured: effectiveModalEntries(this.config).some((e) => e.tokenId.trim() !== "" && e.tokenSecret.trim() !== "")
88703
+ configured: effectiveModalEntries(this.config).some((e) => {
88704
+ const secret = typeof e.tokenSecret === "string" ? e.tokenSecret : "";
88705
+ return (e.tokenId ?? "").trim() !== "" && secret.trim() !== "";
88706
+ })
88563
88707
  } });
88564
88708
  return { providers: [{
88565
88709
  capability: require_dist.trainingProviderCapability,
@@ -88577,15 +88721,18 @@ var TrainingModalAddon = class extends require_dist.BaseAddon {
88577
88721
  gatewayFor(entryId) {
88578
88722
  const entry = findModalEntry(this.config, entryId);
88579
88723
  if (entry === null) return null;
88580
- if (entry.tokenId.trim() === "" || entry.tokenSecret.trim() === "") return null;
88581
- const key = `${entry.tokenId}\u0000${entry.environment}`;
88724
+ const tokenId = (entry.tokenId ?? "").trim();
88725
+ const tokenSecret = (typeof entry.tokenSecret === "string" ? entry.tokenSecret : "").trim();
88726
+ if (tokenId === "" || tokenSecret === "") return null;
88727
+ const environment = (entry.environment ?? "").trim();
88728
+ const key = `${tokenId}\u0000${environment}`;
88582
88729
  const cached = this.gateways.get(entryId);
88583
88730
  if (cached !== void 0 && cached.key === key) return cached.gateway;
88584
88731
  cached?.gateway.close();
88585
88732
  const gateway = new ModalSdkGateway({
88586
- tokenId: entry.tokenId.trim(),
88587
- tokenSecret: entry.tokenSecret.trim(),
88588
- environment: entry.environment.trim()
88733
+ tokenId,
88734
+ tokenSecret,
88735
+ environment
88589
88736
  });
88590
88737
  this.gateways.set(entryId, {
88591
88738
  key,
@@ -88599,6 +88746,19 @@ var TrainingModalAddon = class extends require_dist.BaseAddon {
88599
88746
  cached.gateway.close();
88600
88747
  this.gateways.delete(entryId);
88601
88748
  }
88749
+ /**
88750
+ * Keychain views for the Providers edit form. Addon-facing reads answer
88751
+ * VALUES (D798), so we cannot recover markers from `readStoredGlobalKey`.
88752
+ * When a secret is present on the resolved entry we emit the deterministic
88753
+ * keychain ref (`ctx.secrets.ref`) so `SecretFieldControl` can reveal.
88754
+ */
88755
+ async readEntrySecretViews() {
88756
+ const map = /* @__PURE__ */ new Map();
88757
+ const secrets = this.ctx.secrets;
88758
+ for (const entry of effectiveModalEntries(this.config)) map.set(entry.id, entryTokenSecretView(entry.tokenSecret, secrets, entry.id));
88759
+ map.set("legacy-default", flatTokenSecretView(this.config.tokenSecret, secrets));
88760
+ return map;
88761
+ }
88602
88762
  ledger() {
88603
88763
  const state = () => {
88604
88764
  if (this.ledgerState === null) throw new Error("training-modal ledger not initialised");
@@ -88623,97 +88783,10 @@ var TrainingModalAddon = class extends require_dist.BaseAddon {
88623
88783
  }
88624
88784
  globalSettingsSchema() {
88625
88785
  return this.schema({ sections: [{
88626
- id: "entries",
88786
+ id: "providers",
88627
88787
  title: "Modal accounts",
88628
- description: "Each entry is one Modal workspace token. Create entries from the Training → Providers tab; edit credentials here. Secrets stay in the CamStack keychain.",
88629
- fields: [{
88630
- type: "editable-array",
88631
- key: "entries",
88632
- label: "Entries",
88633
- itemIdKey: "id",
88634
- /** Create only via trainingProvider.createEntry (auto id). */
88635
- maxRows: 0,
88636
- emptyMessage: "No Modal entries yet — add one from Training → Providers.",
88637
- rowTitleTemplate: "{name}",
88638
- defaultItem: {
88639
- id: "",
88640
- name: "Default",
88641
- tokenId: "",
88642
- tokenSecret: "",
88643
- environment: "",
88644
- gpuType: "L4",
88645
- region: "",
88646
- costCapUsd: 5,
88647
- maxRuntimeHours: 4,
88648
- failedDatasetGraceHours: 24
88649
- },
88650
- itemFields: [
88651
- {
88652
- type: "text",
88653
- key: "name",
88654
- label: "Name"
88655
- },
88656
- {
88657
- type: "text",
88658
- key: "tokenId",
88659
- label: "Token id",
88660
- placeholder: "ak-…"
88661
- },
88662
- {
88663
- type: "password",
88664
- key: "tokenSecret",
88665
- label: "Token secret",
88666
- showToggle: true
88667
- },
88668
- {
88669
- type: "text",
88670
- key: "environment",
88671
- label: "Environment",
88672
- description: "Leave empty for the workspace default."
88673
- },
88674
- {
88675
- type: "select",
88676
- key: "gpuType",
88677
- label: "GPU",
88678
- options: MODAL_GPUS.map((g) => ({
88679
- value: g.id,
88680
- label: `${g.label} — $${g.usdPerHour.toFixed(2)}/h`
88681
- }))
88682
- },
88683
- {
88684
- type: "select",
88685
- key: "region",
88686
- label: "Region",
88687
- options: MODAL_REGIONS.map((r) => ({
88688
- value: r.id,
88689
- label: r.label
88690
- }))
88691
- },
88692
- {
88693
- type: "number",
88694
- key: "costCapUsd",
88695
- label: "Cost cap per job (USD)",
88696
- min: .5,
88697
- step: .5
88698
- },
88699
- {
88700
- type: "number",
88701
- key: "maxRuntimeHours",
88702
- label: "Maximum runtime (hours)",
88703
- min: .5,
88704
- max: 24,
88705
- step: .5
88706
- },
88707
- {
88708
- type: "number",
88709
- key: "failedDatasetGraceHours",
88710
- label: "Keep a failed job’s dataset (hours)",
88711
- min: 0,
88712
- max: 168,
88713
- step: 1
88714
- }
88715
- ]
88716
- }]
88788
+ description: "Create and edit Modal workspace entries under Training → Providers. Credentials stay in the CamStack keychain.",
88789
+ fields: []
88717
88790
  }] });
88718
88791
  }
88719
88792
  };