@linxin666/dsh-client-ui-task-board 0.3.19 → 0.3.21
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/README.i18n.yaml +2 -2
- package/README.md +2 -0
- package/README.zh.md +2 -0
- package/lib/client.js +186 -52
- package/lib/client.js.map +1 -1
- package/lib/index.js +17 -7
- package/lib/types/client/body-mutations.d.ts +45 -0
- package/lib/types/client/panel-mount-core.d.ts +1 -1
- package/lib/types/client/sidebar-entry-core.d.ts +0 -15
- package/lib/types/core/controller.d.ts +2 -3
- package/lib/types/core/tasks.d.ts +6 -1
- package/lib/types/core/use-cases/task-archive.d.ts +6 -6
- package/package.json +19 -19
- package/src/client/board/TaskDetail.tsx +4 -2
- package/src/client/body-mutations.ts +133 -0
- package/src/client/index.ts +52 -20
- package/src/client/panel-mount-core.ts +21 -13
- package/src/client/sidebar-entry-core.ts +10 -7
- package/src/core/controller.ts +2 -3
- package/src/core/schedule.ts +4 -1
- package/src/core/tasks.ts +7 -2
- package/src/core/use-cases/task-archive.ts +6 -6
package/README.i18n.yaml
CHANGED
|
@@ -2,5 +2,5 @@
|
|
|
2
2
|
# side as of the last confirmed-consistent state. Both languages carry equal authority;
|
|
3
3
|
# after editing either side, bring the other along and re-record with:
|
|
4
4
|
# node scripts/verify-docs.mjs --write <dir>
|
|
5
|
-
README.md:
|
|
6
|
-
README.zh.md:
|
|
5
|
+
README.md: dfc7909337ae3a340e6330b175f8e275b082c4bc
|
|
6
|
+
README.zh.md: deb74cbc58f4fb06e08fe7b2544ebc9f7e489d10
|
package/README.md
CHANGED
|
@@ -27,6 +27,8 @@ A hot-pluggable DeepSeek Harness (DSH) Web GUI plugin with a Host-authoritative
|
|
|
27
27
|
|
|
28
28
|
## Architecture and protocol
|
|
29
29
|
|
|
30
|
+
The board view renders on first open and keeps its local view state when closed and reopened. Host synchronization, scheduling and execution remain active independently of the view.
|
|
31
|
+
|
|
30
32
|
- `src/index.ts` mounts the Host service through the official `@deepseek-ai/dsh-api-gateway`, `@deepseek-ai/dsh-workspace`, and `@deepseek-ai/dsh-host-webserver` SDKs.
|
|
31
33
|
- `src/host-ledger.ts` serializes actions and persists `{ schemaVersion: 3, revision, tasks, scheduler, recentRequests }` through a temporary file plus atomic rename.
|
|
32
34
|
- `src/host-service.ts` owns cron ticks, missed-trigger skipping, runner launch, restart reconciliation, and power reasons.
|
package/README.zh.md
CHANGED
|
@@ -27,6 +27,8 @@
|
|
|
27
27
|
|
|
28
28
|
## 架构与协议
|
|
29
29
|
|
|
30
|
+
看板视图在首次打开时渲染,关闭后重新打开会保留本地视图状态。Host 同步、调度和执行独立于视图持续运行。
|
|
31
|
+
|
|
30
32
|
- `src/index.ts` 通过官方 `@deepseek-ai/dsh-api-gateway`、`@deepseek-ai/dsh-workspace` 与 `@deepseek-ai/dsh-host-webserver` SDK 挂载 Host 服务。
|
|
31
33
|
- `src/host-ledger.ts` 串行动作,并用临时文件加原子 rename 持久化 `{ schemaVersion: 3, revision, tasks, scheduler, recentRequests }`。
|
|
32
34
|
- `src/host-service.ts` 负责 cron tick、错过触发跳过、runner 启动、重启对账和电源保护理由。
|
package/lib/client.js
CHANGED
|
@@ -8,8 +8,18 @@ window.__ModuleLoader__.load({
|
|
|
8
8
|
let react_jsx_runtime = require("react/jsx-runtime");
|
|
9
9
|
let react_dom_client = require("react-dom/client");
|
|
10
10
|
//#region src/core/tasks.ts
|
|
11
|
-
/**
|
|
12
|
-
|
|
11
|
+
/**
|
|
12
|
+
* Statuses a task may be archived from: every status but `running`, whose
|
|
13
|
+
* execution the runner still owns until it settles. A settled-only gate made
|
|
14
|
+
* the duplicate-and-archive flow a silent no-op for scheduled tasks, which
|
|
15
|
+
* return to `todo` after every successful run (issue #1447).
|
|
16
|
+
*/
|
|
17
|
+
const ARCHIVABLE_STATUSES = [
|
|
18
|
+
"backlog",
|
|
19
|
+
"todo",
|
|
20
|
+
"done",
|
|
21
|
+
"failed"
|
|
22
|
+
];
|
|
13
23
|
/** Permission presets a task may pin on its execution session (the `/permission <id>` ids). */
|
|
14
24
|
const TASK_PERMISSIONS = [
|
|
15
25
|
"read-only",
|
|
@@ -145,10 +155,10 @@ window.__ModuleLoader__.load({
|
|
|
145
155
|
//#endregion
|
|
146
156
|
//#region src/core/use-cases/task-archive.ts
|
|
147
157
|
/**
|
|
148
|
-
* Archive one task: only
|
|
149
|
-
*
|
|
150
|
-
*
|
|
151
|
-
*
|
|
158
|
+
* Archive one task: only a `running` task stays on the board (its runner
|
|
159
|
+
* still owns its lifecycle until the execution settles); every other status
|
|
160
|
+
* can be archived. Archiving disarms a schedule; already-archived tasks are
|
|
161
|
+
* a no-op.
|
|
152
162
|
*/
|
|
153
163
|
function applyArchiveTask(tasks, id, now) {
|
|
154
164
|
let applied = false;
|
|
@@ -338,7 +348,7 @@ window.__ModuleLoader__.load({
|
|
|
338
348
|
high = Number(b);
|
|
339
349
|
} else if (isDigits(range)) {
|
|
340
350
|
low = Number(range);
|
|
341
|
-
high =
|
|
351
|
+
high = stepRaw === void 0 ? low : max;
|
|
342
352
|
} else return false;
|
|
343
353
|
if (low < min || high > max || low > high) return false;
|
|
344
354
|
const step = stepRaw === void 0 ? 1 : isDigits(stepRaw) ? Number(stepRaw) : NaN;
|
|
@@ -751,9 +761,8 @@ window.__ModuleLoader__.load({
|
|
|
751
761
|
this.persistAndNotify();
|
|
752
762
|
}
|
|
753
763
|
/**
|
|
754
|
-
* Archive a
|
|
755
|
-
*
|
|
756
|
-
* lifecycle.
|
|
764
|
+
* Archive a task from any status but `running`, whose lifecycle the runner
|
|
765
|
+
* keeps exclusive ownership of until it settles.
|
|
757
766
|
* @returns true when applied.
|
|
758
767
|
*/
|
|
759
768
|
archiveTask(id) {
|
|
@@ -3015,10 +3024,10 @@ window.__ModuleLoader__.load({
|
|
|
3015
3024
|
}),
|
|
3016
3025
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", {
|
|
3017
3026
|
className: board_module_css_default.executionList,
|
|
3018
|
-
children: current.handover.references.map((reference) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("li", {
|
|
3027
|
+
children: current.handover.references.map((reference, index) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("li", {
|
|
3019
3028
|
className: board_module_css_default.executionRow,
|
|
3020
3029
|
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("code", { children: reference })
|
|
3021
|
-
}, reference))
|
|
3030
|
+
}, `${reference}-${index}`))
|
|
3022
3031
|
}),
|
|
3023
3032
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
3024
3033
|
className: board_module_css_default.detailMeta,
|
|
@@ -3414,6 +3423,94 @@ window.__ModuleLoader__.load({
|
|
|
3414
3423
|
});
|
|
3415
3424
|
}
|
|
3416
3425
|
//#endregion
|
|
3426
|
+
//#region src/client/body-mutations.ts
|
|
3427
|
+
/** Cross-bundle registry key; `Symbol.for` so every module copy agrees. */
|
|
3428
|
+
const HUB_KEY = Symbol.for("dsh-web.body-mutation-hub");
|
|
3429
|
+
const INVALIDATION_ONLY = Symbol.for("dsh-web.body-mutation-invalidation");
|
|
3430
|
+
function needsRecords(subscribers) {
|
|
3431
|
+
for (const listener of subscribers) if (!listener[INVALIDATION_ONLY]) return true;
|
|
3432
|
+
return false;
|
|
3433
|
+
}
|
|
3434
|
+
/**
|
|
3435
|
+
* Subscribe to a coalesced DOM re-check without retaining mutation records.
|
|
3436
|
+
* The marked wrapper also works with an older hub, which delivers records
|
|
3437
|
+
* that it simply ignores until a page reload picks up the updated hub.
|
|
3438
|
+
*/
|
|
3439
|
+
function subscribeBodyInvalidations(subscriber) {
|
|
3440
|
+
const listener = () => {
|
|
3441
|
+
subscriber();
|
|
3442
|
+
};
|
|
3443
|
+
listener[INVALIDATION_ONLY] = true;
|
|
3444
|
+
return subscribeBodyMutations(listener);
|
|
3445
|
+
}
|
|
3446
|
+
/**
|
|
3447
|
+
* Subscribe to body-level childList mutations.
|
|
3448
|
+
* @param subscriber - called at most once per animation frame with the records
|
|
3449
|
+
* collected since the previous flush; must be safe to run repeatedly.
|
|
3450
|
+
* @returns the disposer removing this subscriber (and the observer when it was
|
|
3451
|
+
* the last one).
|
|
3452
|
+
*/
|
|
3453
|
+
function subscribeBodyMutations(subscriber) {
|
|
3454
|
+
if (typeof globalThis === "undefined" || typeof document === "undefined") return () => {};
|
|
3455
|
+
if (typeof MutationObserver !== "function") return () => {};
|
|
3456
|
+
const registry = globalThis;
|
|
3457
|
+
let hub = registry[HUB_KEY];
|
|
3458
|
+
if (hub === void 0) {
|
|
3459
|
+
const subscribers = /* @__PURE__ */ new Set();
|
|
3460
|
+
const created = {
|
|
3461
|
+
observer: void 0,
|
|
3462
|
+
subscribers,
|
|
3463
|
+
pending: [],
|
|
3464
|
+
scheduled: false
|
|
3465
|
+
};
|
|
3466
|
+
const flush = () => {
|
|
3467
|
+
created.frame = void 0;
|
|
3468
|
+
created.scheduled = false;
|
|
3469
|
+
const batch = created.pending;
|
|
3470
|
+
created.pending = [];
|
|
3471
|
+
for (const listener of [...subscribers]) {
|
|
3472
|
+
if (!subscribers.has(listener)) continue;
|
|
3473
|
+
try {
|
|
3474
|
+
listener(batch);
|
|
3475
|
+
} catch {}
|
|
3476
|
+
}
|
|
3477
|
+
};
|
|
3478
|
+
const schedule = () => {
|
|
3479
|
+
if (created.scheduled) return;
|
|
3480
|
+
created.scheduled = true;
|
|
3481
|
+
if (typeof requestAnimationFrame === "function") created.frame = requestAnimationFrame(flush);
|
|
3482
|
+
else flush();
|
|
3483
|
+
};
|
|
3484
|
+
created.observer = new MutationObserver((records) => {
|
|
3485
|
+
if (needsRecords(subscribers)) for (const record of records) created.pending.push(record);
|
|
3486
|
+
schedule();
|
|
3487
|
+
});
|
|
3488
|
+
created.observer.observe(document.body ?? document.documentElement, {
|
|
3489
|
+
childList: true,
|
|
3490
|
+
subtree: true
|
|
3491
|
+
});
|
|
3492
|
+
registry[HUB_KEY] = created;
|
|
3493
|
+
hub = created;
|
|
3494
|
+
}
|
|
3495
|
+
const active = hub;
|
|
3496
|
+
active.subscribers.add(subscriber);
|
|
3497
|
+
let subscribed = true;
|
|
3498
|
+
return () => {
|
|
3499
|
+
if (!subscribed) return;
|
|
3500
|
+
subscribed = false;
|
|
3501
|
+
active.subscribers.delete(subscriber);
|
|
3502
|
+
if (!needsRecords(active.subscribers)) active.pending = [];
|
|
3503
|
+
if (active.subscribers.size === 0 && registry[HUB_KEY] === active) {
|
|
3504
|
+
active.observer.disconnect();
|
|
3505
|
+
if (active.frame !== void 0 && typeof cancelAnimationFrame === "function") cancelAnimationFrame(active.frame);
|
|
3506
|
+
active.frame = void 0;
|
|
3507
|
+
active.pending = [];
|
|
3508
|
+
active.scheduled = false;
|
|
3509
|
+
delete registry[HUB_KEY];
|
|
3510
|
+
}
|
|
3511
|
+
};
|
|
3512
|
+
}
|
|
3513
|
+
//#endregion
|
|
3417
3514
|
//#region src/client/panel-mount-core.ts
|
|
3418
3515
|
/**
|
|
3419
3516
|
* Center-column panel takeover lifecycle.
|
|
@@ -3458,32 +3555,31 @@ window.__ModuleLoader__.load({
|
|
|
3458
3555
|
});
|
|
3459
3556
|
} catch {}
|
|
3460
3557
|
const ensure = () => {
|
|
3461
|
-
if (container !== void 0) {
|
|
3462
|
-
if (container.isConnected) return;
|
|
3558
|
+
if (container !== void 0 && !container.isConnected) {
|
|
3463
3559
|
root?.unmount();
|
|
3464
3560
|
root = void 0;
|
|
3465
3561
|
container.remove();
|
|
3466
3562
|
container = void 0;
|
|
3467
3563
|
}
|
|
3468
|
-
|
|
3469
|
-
|
|
3470
|
-
|
|
3471
|
-
|
|
3472
|
-
|
|
3473
|
-
|
|
3474
|
-
|
|
3564
|
+
if (container === void 0) {
|
|
3565
|
+
const column = conversationColumn();
|
|
3566
|
+
if (column === void 0) return;
|
|
3567
|
+
container = document.createElement("div");
|
|
3568
|
+
container.dataset[options.viewDatasetKey] = "";
|
|
3569
|
+
container.dataset.dshPlugin = options.pluginName;
|
|
3570
|
+
container.className = options.viewClassName;
|
|
3571
|
+
column.appendChild(container);
|
|
3572
|
+
}
|
|
3573
|
+
if (root !== void 0 || !options.isOpen()) return;
|
|
3475
3574
|
root = (0, react_dom_client.createRoot)(container);
|
|
3476
3575
|
options.render(root);
|
|
3477
3576
|
};
|
|
3478
|
-
const
|
|
3577
|
+
const unsubscribeBody = subscribeBodyInvalidations(() => {
|
|
3479
3578
|
ensure();
|
|
3480
3579
|
});
|
|
3481
|
-
waitObserver.observe(document.body, {
|
|
3482
|
-
childList: true,
|
|
3483
|
-
subtree: true
|
|
3484
|
-
});
|
|
3485
3580
|
const applyActive = () => {
|
|
3486
3581
|
if (options.isOpen()) {
|
|
3582
|
+
ensure();
|
|
3487
3583
|
document.documentElement.removeAttribute(options.siblingActiveAttribute);
|
|
3488
3584
|
document.documentElement.setAttribute(options.activeAttribute, "");
|
|
3489
3585
|
document.dispatchEvent(new CustomEvent(ACTIVATE_EVENT, { detail: options.panelName }));
|
|
@@ -3506,7 +3602,7 @@ window.__ModuleLoader__.load({
|
|
|
3506
3602
|
return () => {
|
|
3507
3603
|
document.removeEventListener("click", onClickSidebarRow, true);
|
|
3508
3604
|
document.removeEventListener(ACTIVATE_EVENT, onOtherActivate);
|
|
3509
|
-
|
|
3605
|
+
unsubscribeBody();
|
|
3510
3606
|
unsubscribe();
|
|
3511
3607
|
unsubscribeLocale?.();
|
|
3512
3608
|
document.documentElement.removeAttribute(options.activeAttribute);
|
|
@@ -3544,6 +3640,21 @@ window.__ModuleLoader__.load({
|
|
|
3544
3640
|
}
|
|
3545
3641
|
//#endregion
|
|
3546
3642
|
//#region src/client/sidebar-entry-core.ts
|
|
3643
|
+
/**
|
|
3644
|
+
* Shared sidebar entry injection core.
|
|
3645
|
+
*
|
|
3646
|
+
* dsh's sidebar shell exposes no slot an external plugin can register into,
|
|
3647
|
+
* so the entry row is injected between the shell's New Session button and the
|
|
3648
|
+
* workspace browser. The injection self-heals: a MutationObserver watches the
|
|
3649
|
+
* sidebar root and re-inserts the row whenever a React re-render displaces it
|
|
3650
|
+
* (re-insertion happens in the same frame, before paint, so no flicker).
|
|
3651
|
+
*
|
|
3652
|
+
* The row is plain DOM (no React tree) so it can never disturb the shell's
|
|
3653
|
+
* reconciliation; the view it toggles is a separate root owned by the caller.
|
|
3654
|
+
*
|
|
3655
|
+
* Packages receive this file as a generated copy via scripts/sync-shared.mjs;
|
|
3656
|
+
* edit the shared source and re-run the sync instead of editing a copy.
|
|
3657
|
+
*/
|
|
3547
3658
|
/** Find the sidebar shell root element, or undefined while not yet mounted. */
|
|
3548
3659
|
function sidebarRoot() {
|
|
3549
3660
|
const column = document.querySelector("[data-pane=\"sidebar\"], [class*=\"sidebarCol\"]");
|
|
@@ -3632,13 +3743,9 @@ window.__ModuleLoader__.load({
|
|
|
3632
3743
|
subtree: true
|
|
3633
3744
|
});
|
|
3634
3745
|
};
|
|
3635
|
-
const
|
|
3746
|
+
const unsubscribeBody = subscribeBodyInvalidations(() => {
|
|
3636
3747
|
tryPlace();
|
|
3637
3748
|
});
|
|
3638
|
-
waitObserver.observe(document.body, {
|
|
3639
|
-
childList: true,
|
|
3640
|
-
subtree: true
|
|
3641
|
-
});
|
|
3642
3749
|
const rootObserver = new MutationObserver(() => {
|
|
3643
3750
|
if (root === void 0 || !root.isConnected) {
|
|
3644
3751
|
placed = false;
|
|
@@ -3658,7 +3765,7 @@ window.__ModuleLoader__.load({
|
|
|
3658
3765
|
})();
|
|
3659
3766
|
tryPlace();
|
|
3660
3767
|
return () => {
|
|
3661
|
-
|
|
3768
|
+
unsubscribeBody();
|
|
3662
3769
|
rootObserver.disconnect();
|
|
3663
3770
|
unsubscribeRefresh?.();
|
|
3664
3771
|
unsubscribeActive?.();
|
|
@@ -4636,7 +4743,7 @@ window.__ModuleLoader__.load({
|
|
|
4636
4743
|
/** The building package's version, when the bundle carries it. */
|
|
4637
4744
|
function bakedVersion() {
|
|
4638
4745
|
try {
|
|
4639
|
-
return "0.3.
|
|
4746
|
+
return "0.3.21";
|
|
4640
4747
|
} catch {
|
|
4641
4748
|
return;
|
|
4642
4749
|
}
|
|
@@ -4719,7 +4826,8 @@ window.__ModuleLoader__.load({
|
|
|
4719
4826
|
"connection",
|
|
4720
4827
|
"settingsScope",
|
|
4721
4828
|
"locale",
|
|
4722
|
-
"remote"
|
|
4829
|
+
"remote",
|
|
4830
|
+
"remote.session"
|
|
4723
4831
|
];
|
|
4724
4832
|
/**
|
|
4725
4833
|
* Mount the task board.
|
|
@@ -4766,7 +4874,7 @@ window.__ModuleLoader__.load({
|
|
|
4766
4874
|
if (uiDisposer !== void 0) return;
|
|
4767
4875
|
const sessions = ctx.get("sessions");
|
|
4768
4876
|
const workspaces = ctx.get("workspaces");
|
|
4769
|
-
ctx.get("remote");
|
|
4877
|
+
const remote = ctx.get("remote");
|
|
4770
4878
|
const controller = new BoardController({
|
|
4771
4879
|
store: new LocalStorageTaskStore(),
|
|
4772
4880
|
transport: new HttpTaskBoardHostTransport(),
|
|
@@ -4788,29 +4896,55 @@ window.__ModuleLoader__.load({
|
|
|
4788
4896
|
disposers.push(workspaces.list.subscribe(pushWorkspaceOptions));
|
|
4789
4897
|
const pushModelOptions = async () => {
|
|
4790
4898
|
try {
|
|
4791
|
-
|
|
4792
|
-
|
|
4793
|
-
|
|
4794
|
-
|
|
4795
|
-
|
|
4796
|
-
|
|
4797
|
-
|
|
4798
|
-
|
|
4799
|
-
|
|
4800
|
-
|
|
4801
|
-
|
|
4802
|
-
const
|
|
4803
|
-
|
|
4804
|
-
const qualifiedId = g.provider ? `${g.provider}/${m.id}` : m.id;
|
|
4899
|
+
let models = [];
|
|
4900
|
+
let sessionRemote;
|
|
4901
|
+
try {
|
|
4902
|
+
sessionRemote = remote.session;
|
|
4903
|
+
} catch {
|
|
4904
|
+
sessionRemote = void 0;
|
|
4905
|
+
}
|
|
4906
|
+
if (typeof sessionRemote?.modelCatalog === "function") {
|
|
4907
|
+
const res = await sessionRemote.modelCatalog();
|
|
4908
|
+
if (res.ok && Array.isArray(res.value?.groups)) for (const g of res.value.groups) {
|
|
4909
|
+
const provider = g.id ?? g.provider;
|
|
4910
|
+
for (const m of g.models ?? []) {
|
|
4911
|
+
const qualifiedId = provider ? `${provider}/${m.id}` : m.id;
|
|
4805
4912
|
models.push({
|
|
4806
4913
|
id: qualifiedId,
|
|
4807
4914
|
name: m.name ?? m.id,
|
|
4808
|
-
provider
|
|
4915
|
+
provider
|
|
4809
4916
|
});
|
|
4810
4917
|
}
|
|
4811
4918
|
}
|
|
4812
|
-
if (models.length > 0) controller.setExecutionOptions({ models });
|
|
4813
4919
|
}
|
|
4920
|
+
if (models.length === 0) {
|
|
4921
|
+
const conn = ctx.get("connection");
|
|
4922
|
+
if (conn?.api) {
|
|
4923
|
+
if (typeof conn.api.llm?.discoverModels === "function") {
|
|
4924
|
+
const list = (await conn.api.llm.discoverModels())?.result?.value?.models;
|
|
4925
|
+
if (Array.isArray(list)) models = list.map((m) => ({
|
|
4926
|
+
id: m.id,
|
|
4927
|
+
name: m.name
|
|
4928
|
+
}));
|
|
4929
|
+
}
|
|
4930
|
+
const catalogFn = typeof conn.api.session?.modelCatalog === "function" ? conn.api.session.modelCatalog : typeof conn.api.sessions?.modelCatalog === "function" ? conn.api.sessions.modelCatalog : void 0;
|
|
4931
|
+
if (models.length === 0 && catalogFn !== void 0) {
|
|
4932
|
+
const groups = (await catalogFn())?.result?.value?.groups;
|
|
4933
|
+
if (Array.isArray(groups)) for (const g of groups) {
|
|
4934
|
+
const provider = g.id ?? g.provider;
|
|
4935
|
+
for (const m of g.models ?? []) {
|
|
4936
|
+
const qualifiedId = provider ? `${provider}/${m.id}` : m.id;
|
|
4937
|
+
models.push({
|
|
4938
|
+
id: qualifiedId,
|
|
4939
|
+
name: m.name ?? m.id,
|
|
4940
|
+
provider
|
|
4941
|
+
});
|
|
4942
|
+
}
|
|
4943
|
+
}
|
|
4944
|
+
}
|
|
4945
|
+
}
|
|
4946
|
+
}
|
|
4947
|
+
if (models.length > 0) controller.setExecutionOptions({ models });
|
|
4814
4948
|
} catch (error) {
|
|
4815
4949
|
console.error("[dsh-task-board] model options read failed", error);
|
|
4816
4950
|
}
|