@linxin666/dsh-client-ui-task-board 0.3.5 → 0.3.9
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 +9 -4
- package/README.zh.md +9 -4
- package/lib/client.js +947 -354
- package/lib/client.js.map +1 -1
- package/lib/index.js +712 -180
- package/lib/types/client/TaskBoardSettingsCard.d.ts +2 -1
- package/lib/types/client/board/TaskBoard.d.ts +3 -0
- package/lib/types/client/board/TaskForm.d.ts +28 -0
- package/lib/types/client/host-api.d.ts +2 -2
- package/lib/types/client/index.d.ts +10 -2
- package/lib/types/client/locales.d.ts +19 -0
- package/lib/types/client/settings-form.d.ts +35 -43
- package/lib/types/core/controller.d.ts +11 -2
- package/lib/types/core/freeze-snapshot.d.ts +71 -0
- package/lib/types/core/handover.d.ts +58 -0
- package/lib/types/core/tasks.d.ts +70 -1
- package/lib/types/core/use-cases/task-update.d.ts +16 -7
- package/lib/types/host-ledger.d.ts +17 -2
- package/lib/types/host-runner.d.ts +44 -17
- package/lib/types/host-service.d.ts +8 -5
- package/lib/types/index.d.ts +9 -1
- package/lib/types/protocol.d.ts +15 -2
- package/package.json +26 -17
- package/src/client/TaskBoardSettingsCard.tsx +2 -1
- package/src/client/board/EditTaskModal.tsx +19 -55
- package/src/client/board/NewTaskModal.tsx +59 -42
- package/src/client/board/TaskBoard.tsx +5 -3
- package/src/client/board/TaskCard.tsx +3 -0
- package/src/client/board/TaskDetail.tsx +54 -0
- package/src/client/board/TaskForm.tsx +111 -0
- package/src/client/board-mount.tsx +7 -1
- package/src/client/host-api.ts +5 -5
- package/src/client/index.ts +67 -7
- package/src/client/locales.ts +40 -2
- package/src/client/settings-form.ts +78 -92
- package/src/core/controller.ts +36 -16
- package/src/core/freeze-snapshot.ts +213 -0
- package/src/core/handover.ts +117 -0
- package/src/core/store.ts +48 -1
- package/src/core/tasks.ts +87 -0
- package/src/core/use-cases/task-update.ts +30 -10
- package/src/dsh-home.ts +1 -1
- package/src/host-ledger.ts +137 -59
- package/src/host-routes.ts +1 -1
- package/src/host-runner.ts +223 -83
- package/src/host-service.ts +14 -10
- package/src/index.ts +24 -10
- package/src/protocol.ts +98 -14
package/lib/client.js
CHANGED
|
@@ -7,7 +7,6 @@ window.__ModuleLoader__.load({
|
|
|
7
7
|
let react_dom_client = require("react-dom/client");
|
|
8
8
|
let react = require("react");
|
|
9
9
|
let react_jsx_runtime = require("react/jsx-runtime");
|
|
10
|
-
let _deepseek_ai_dsh_client_runtime_client = require("@deepseek-ai/dsh-client-runtime/client");
|
|
11
10
|
//#region src/core/tasks.ts
|
|
12
11
|
/** Statuses a settled task may be archived from. */
|
|
13
12
|
const ARCHIVABLE_STATUSES = ["done", "failed"];
|
|
@@ -67,6 +66,20 @@ window.__ModuleLoader__.load({
|
|
|
67
66
|
const trimmed = value?.trim();
|
|
68
67
|
return trimmed === void 0 || trimmed === "" ? void 0 : trimmed;
|
|
69
68
|
}
|
|
69
|
+
/**
|
|
70
|
+
* Build the persisted freeze snapshot from a sanitized input, stamping the
|
|
71
|
+
* freeze instant (shared by the create and update use cases).
|
|
72
|
+
*/
|
|
73
|
+
function freezeOf(input, now) {
|
|
74
|
+
return {
|
|
75
|
+
goal: input.goal,
|
|
76
|
+
progress: input.progress,
|
|
77
|
+
next: input.next,
|
|
78
|
+
frozenAt: now,
|
|
79
|
+
...input.redacted === true ? { redacted: true } : {},
|
|
80
|
+
...input.frozenBy === void 0 || input.frozenBy === "" ? {} : { frozenBy: input.frozenBy }
|
|
81
|
+
};
|
|
82
|
+
}
|
|
70
83
|
/** Create a task from user input. */
|
|
71
84
|
function createTask(input, now, id) {
|
|
72
85
|
return {
|
|
@@ -80,7 +93,12 @@ window.__ModuleLoader__.load({
|
|
|
80
93
|
executions: [],
|
|
81
94
|
workspaceId: normalizeTargetId(input.workspaceId),
|
|
82
95
|
mode: normalizeTargetId(input.mode),
|
|
83
|
-
permission: isTaskPermission(input.permission) ? input.permission : void 0
|
|
96
|
+
permission: isTaskPermission(input.permission) ? input.permission : void 0,
|
|
97
|
+
...input.freeze === void 0 ? {} : { freeze: freezeOf(input.freeze, now) },
|
|
98
|
+
...input.handover === void 0 ? {} : { handover: {
|
|
99
|
+
...input.handover,
|
|
100
|
+
bundledAt: now
|
|
101
|
+
} }
|
|
84
102
|
};
|
|
85
103
|
}
|
|
86
104
|
/** Clone a task with an updated status and a fresh updatedAt. */
|
|
@@ -455,13 +473,11 @@ window.__ModuleLoader__.load({
|
|
|
455
473
|
* fresh updatedAt. Pure ledger transition (no persistence or notify — the
|
|
456
474
|
* controller orchestrates those).
|
|
457
475
|
*
|
|
458
|
-
* An explicit `undefined` in the patch clears
|
|
459
|
-
*
|
|
460
|
-
*
|
|
461
|
-
* can never persist a value the execution service rejects.
|
|
476
|
+
* An explicit `undefined` in the patch clears the field (the task falls
|
|
477
|
+
* back to the runtime default); an unknown permission string is ignored so
|
|
478
|
+
* stale UI can never persist a value the execution service rejects.
|
|
462
479
|
*/
|
|
463
|
-
/**
|
|
464
|
-
* The fields that edit the task's content (what the user reads and what the
|
|
480
|
+
/** The fields that edit the task's content (what the user reads and what the
|
|
465
481
|
* next execution sends). Unlike the execution targets they stay editable only
|
|
466
482
|
* while the task has never started executing — after the first run the
|
|
467
483
|
* recorded prompt is the record of what actually ran, so it becomes read-only.
|
|
@@ -495,12 +511,13 @@ window.__ModuleLoader__.load({
|
|
|
495
511
|
function applyUpdateTask(tasks, id, patch, now) {
|
|
496
512
|
return tasks.map((task) => {
|
|
497
513
|
if (task.id !== id) return task;
|
|
514
|
+
const { freeze: freezePatch, handover: handoverPatch, ...rest } = patch;
|
|
498
515
|
const workspaceId = "workspaceId" in patch ? normalizeTargetId(patch.workspaceId) : void 0;
|
|
499
516
|
const mode = "mode" in patch ? normalizeTargetId(patch.mode) : void 0;
|
|
500
517
|
const permission = "permission" in patch ? normalizePermission(task.permission, patch.permission) : void 0;
|
|
501
518
|
const next = {
|
|
502
519
|
...task,
|
|
503
|
-
...
|
|
520
|
+
...rest,
|
|
504
521
|
updatedAt: now
|
|
505
522
|
};
|
|
506
523
|
for (const field of TASK_CONTENT_FIELDS) {
|
|
@@ -508,6 +525,12 @@ window.__ModuleLoader__.load({
|
|
|
508
525
|
const value = patch[field];
|
|
509
526
|
next[field] = value === void 0 ? task[field] : value.trim();
|
|
510
527
|
}
|
|
528
|
+
next.freeze = freezePatch == null ? void 0 : freezeOf(freezePatch, now);
|
|
529
|
+
next.handover = handoverPatch == null ? void 0 : {
|
|
530
|
+
...handoverPatch,
|
|
531
|
+
bundledAt: now
|
|
532
|
+
};
|
|
533
|
+
if ("permission" in patch && patch.permission !== void 0 && patch.permission !== task.permission || "handover" in patch) next.permissionConfirmedAt = void 0;
|
|
511
534
|
if (workspaceId !== void 0 || "workspaceId" in patch) next.workspaceId = workspaceId;
|
|
512
535
|
if (mode !== void 0 || "mode" in patch) next.mode = mode;
|
|
513
536
|
if (permission !== void 0 || "permission" in patch) next.permission = permission;
|
|
@@ -516,6 +539,9 @@ window.__ModuleLoader__.load({
|
|
|
516
539
|
}
|
|
517
540
|
//#endregion
|
|
518
541
|
//#region src/core/controller.ts
|
|
542
|
+
function currentOf(sessions) {
|
|
543
|
+
return sessions?.list.getSnapshot().current;
|
|
544
|
+
}
|
|
519
545
|
/** The selected task (resolved from the ledger), or undefined. */
|
|
520
546
|
function selectedTaskOf(snapshot) {
|
|
521
547
|
if (snapshot.selectedTaskId === void 0) return void 0;
|
|
@@ -615,7 +641,6 @@ window.__ModuleLoader__.load({
|
|
|
615
641
|
this.notify();
|
|
616
642
|
}
|
|
617
643
|
closeBoard() {
|
|
618
|
-
if (!this.boardOpen) return;
|
|
619
644
|
this.boardOpen = false;
|
|
620
645
|
this.notify();
|
|
621
646
|
}
|
|
@@ -803,6 +828,7 @@ window.__ModuleLoader__.load({
|
|
|
803
828
|
* @param sessionId - the execution session to open.
|
|
804
829
|
*/
|
|
805
830
|
openSession(sessionId) {
|
|
831
|
+
this.closeBoard();
|
|
806
832
|
this.deps.sessions.open(sessionId);
|
|
807
833
|
}
|
|
808
834
|
/**
|
|
@@ -818,6 +844,26 @@ window.__ModuleLoader__.load({
|
|
|
818
844
|
return await this.commitRemote({
|
|
819
845
|
kind: "run",
|
|
820
846
|
taskId: id
|
|
847
|
+
}, id, currentOf(this.deps.sessions));
|
|
848
|
+
}
|
|
849
|
+
/**
|
|
850
|
+
* Confirm a card's above-default permission binding through the Host
|
|
851
|
+
* (resolves the pending-confirmation transaction; no-op otherwise).
|
|
852
|
+
*/
|
|
853
|
+
async confirmPermission(id) {
|
|
854
|
+
if (this.tasks.find((candidate) => candidate.id === id) === void 0) return false;
|
|
855
|
+
if (this.deps.transport === void 0) {
|
|
856
|
+
this.tasks = this.tasks.map((candidate) => candidate.id === id ? {
|
|
857
|
+
...candidate,
|
|
858
|
+
permissionConfirmedAt: this.now(),
|
|
859
|
+
updatedAt: this.now()
|
|
860
|
+
} : candidate);
|
|
861
|
+
this.persistAndNotify();
|
|
862
|
+
return true;
|
|
863
|
+
}
|
|
864
|
+
return await this.commitRemote({
|
|
865
|
+
kind: "confirm-permission",
|
|
866
|
+
taskId: id
|
|
821
867
|
}, id);
|
|
822
868
|
}
|
|
823
869
|
/** Re-run a settled task through the Host (the Host replans and executes). */
|
|
@@ -828,7 +874,7 @@ window.__ModuleLoader__.load({
|
|
|
828
874
|
await this.commitRemote({
|
|
829
875
|
kind: "rerun",
|
|
830
876
|
taskId: id
|
|
831
|
-
}, id);
|
|
877
|
+
}, id, currentOf(this.deps.sessions));
|
|
832
878
|
}
|
|
833
879
|
/**
|
|
834
880
|
* Session-list notifications fire for all kinds of incidental churn
|
|
@@ -845,10 +891,10 @@ window.__ModuleLoader__.load({
|
|
|
845
891
|
if (this.deps.transport === void 0) this.deps.store.save(this.tasks);
|
|
846
892
|
this.notify();
|
|
847
893
|
}
|
|
848
|
-
async commitRemote(action, taskId) {
|
|
894
|
+
async commitRemote(action, taskId, initiator) {
|
|
849
895
|
if (this.deps.transport === void 0) return true;
|
|
850
|
-
if (taskId === void 0) return await this.performRemote(action);
|
|
851
|
-
const operation = (this.taskQueues.get(taskId) ?? Promise.resolve()).catch(() => {}).then(async () => await this.performRemote(action));
|
|
896
|
+
if (taskId === void 0) return await this.performRemote(action, initiator);
|
|
897
|
+
const operation = (this.taskQueues.get(taskId) ?? Promise.resolve()).catch(() => {}).then(async () => await this.performRemote(action, initiator));
|
|
852
898
|
const tail = operation.then(() => {}, () => {});
|
|
853
899
|
this.taskQueues.set(taskId, tail);
|
|
854
900
|
this.pendingTaskIds.add(taskId);
|
|
@@ -863,13 +909,13 @@ window.__ModuleLoader__.load({
|
|
|
863
909
|
}
|
|
864
910
|
}
|
|
865
911
|
}
|
|
866
|
-
async performRemote(action) {
|
|
912
|
+
async performRemote(action, initiator) {
|
|
867
913
|
const transport = this.deps.transport;
|
|
868
914
|
if (transport === void 0) return true;
|
|
869
915
|
this.transportError = void 0;
|
|
870
916
|
this.notify();
|
|
871
917
|
try {
|
|
872
|
-
return this.acceptRemote(await transport.action(action)) || await this.refreshRemote();
|
|
918
|
+
return this.acceptRemote(await transport.action(action, initiator)) || await this.refreshRemote();
|
|
873
919
|
} catch (error) {
|
|
874
920
|
await this.refreshRemote(messageOf(error));
|
|
875
921
|
return false;
|
|
@@ -955,6 +1001,248 @@ window.__ModuleLoader__.load({
|
|
|
955
1001
|
for (const fn of [...this.listeners]) fn();
|
|
956
1002
|
}
|
|
957
1003
|
};
|
|
1004
|
+
/** Marker replacing every sensitive match. */
|
|
1005
|
+
const REDACTED_MARKER = "[REDACTED]";
|
|
1006
|
+
const BEGIN = "<<<FREEZE";
|
|
1007
|
+
const END = ">>>FREEZE";
|
|
1008
|
+
const SECTION_KEYS = /* @__PURE__ */ new Map([
|
|
1009
|
+
["目标:", "goal"],
|
|
1010
|
+
["进度:", "progress"],
|
|
1011
|
+
["下一步:", "next"]
|
|
1012
|
+
]);
|
|
1013
|
+
const SECTION_ORDER = [
|
|
1014
|
+
"goal",
|
|
1015
|
+
"progress",
|
|
1016
|
+
"next"
|
|
1017
|
+
];
|
|
1018
|
+
const SECTION_NAMES = {
|
|
1019
|
+
goal: "目标",
|
|
1020
|
+
progress: "进度",
|
|
1021
|
+
next: "下一步"
|
|
1022
|
+
};
|
|
1023
|
+
/**
|
|
1024
|
+
* Sensitive patterns, each matched globally over every field body:
|
|
1025
|
+
* PEM private key blocks (whole block collapses to one marker), Bearer
|
|
1026
|
+
* credentials, OpenAI sk-, GitHub ghp_, GitLab glpat-, Slack xox* tokens,
|
|
1027
|
+
* and AWS access key ids.
|
|
1028
|
+
*/
|
|
1029
|
+
const SENSITIVE_PATTERNS = [
|
|
1030
|
+
/-----BEGIN [A-Z ]*PRIVATE KEY-----[\s\S]*?-----END [A-Z ]*PRIVATE KEY-----/g,
|
|
1031
|
+
/Bearer\s+[A-Za-z0-9._~+\/=:-]{8,}/gi,
|
|
1032
|
+
/sk-[A-Za-z0-9_-]{8,}/g,
|
|
1033
|
+
/ghp_[A-Za-z0-9]{20,}/g,
|
|
1034
|
+
/glpat-[A-Za-z0-9_-]{15,}/g,
|
|
1035
|
+
/xox[bpars]-[A-Za-z0-9-]{10,}/g,
|
|
1036
|
+
/AKIA[0-9A-Z]{16}/g
|
|
1037
|
+
];
|
|
1038
|
+
function fail(code, message) {
|
|
1039
|
+
return {
|
|
1040
|
+
ok: false,
|
|
1041
|
+
error: {
|
|
1042
|
+
code,
|
|
1043
|
+
message
|
|
1044
|
+
}
|
|
1045
|
+
};
|
|
1046
|
+
}
|
|
1047
|
+
function utf8Bytes(text) {
|
|
1048
|
+
return new TextEncoder().encode(text).length;
|
|
1049
|
+
}
|
|
1050
|
+
/** Redact sensitive patterns to the marker; reports whether any hit occurred. */
|
|
1051
|
+
function redactSensitive(text) {
|
|
1052
|
+
let out = text;
|
|
1053
|
+
let redacted = false;
|
|
1054
|
+
for (const pattern of SENSITIVE_PATTERNS) {
|
|
1055
|
+
if (pattern.test(out)) {
|
|
1056
|
+
redacted = true;
|
|
1057
|
+
out = out.replace(pattern, REDACTED_MARKER);
|
|
1058
|
+
}
|
|
1059
|
+
pattern.lastIndex = 0;
|
|
1060
|
+
}
|
|
1061
|
+
return {
|
|
1062
|
+
text: out,
|
|
1063
|
+
redacted
|
|
1064
|
+
};
|
|
1065
|
+
}
|
|
1066
|
+
/**
|
|
1067
|
+
* True when any line of the text starts with "/" (a DSH command line).
|
|
1068
|
+
* Leading horizontal whitespace before the slash counts too: a frozen
|
|
1069
|
+
* body line like ` /kill` is still a command, not prose.
|
|
1070
|
+
*/
|
|
1071
|
+
function hasSlashCommandLines(text) {
|
|
1072
|
+
return text.split(/\r?\n/).some((line) => /^[ \t]+\//.test(line) || line.startsWith("/"));
|
|
1073
|
+
}
|
|
1074
|
+
/**
|
|
1075
|
+
* Sanitize a structured freeze snapshot (the create/update action payload):
|
|
1076
|
+
* shape check, slash-command taint rejection, sensitive redaction, and the
|
|
1077
|
+
* per-field byte cap - the same gate parseFreezeRequest applies to
|
|
1078
|
+
* free-text freeze requests, exposed for the action data plane (issue #4).
|
|
1079
|
+
* @param value - the freeze object carried by an action or read back from disk.
|
|
1080
|
+
* @param extraKeys - keys allowed alongside goal/progress/next (e.g. the
|
|
1081
|
+
* protocol redacted flag, the ledger frozenAt stamp) and preserved verbatim
|
|
1082
|
+
* when present; their validation stays with the caller.
|
|
1083
|
+
*/
|
|
1084
|
+
function sanitizeFreezeSnapshot(value, extraKeys = []) {
|
|
1085
|
+
const bad = (code, message) => ({
|
|
1086
|
+
ok: false,
|
|
1087
|
+
error: {
|
|
1088
|
+
code,
|
|
1089
|
+
message
|
|
1090
|
+
}
|
|
1091
|
+
});
|
|
1092
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) return bad("invalid-freeze", "冻结快照必须是 goal/progress/next 字符串对象");
|
|
1093
|
+
const record = value;
|
|
1094
|
+
const allowed = [
|
|
1095
|
+
"goal",
|
|
1096
|
+
"progress",
|
|
1097
|
+
"next",
|
|
1098
|
+
...extraKeys
|
|
1099
|
+
];
|
|
1100
|
+
if (!Object.keys(record).every((key) => allowed.includes(key))) return bad("invalid-freeze", "冻结快照包含未知字段");
|
|
1101
|
+
for (const key of SECTION_ORDER) if (typeof record[key] !== "string") return bad("invalid-freeze", "冻结快照字段 " + SECTION_NAMES[key] + " 必须是字符串");
|
|
1102
|
+
for (const key of SECTION_ORDER) if (hasSlashCommandLines(record[key])) return bad("dsh-command-line", "冻结文本的" + SECTION_NAMES[key] + "包含以 / 开头的命令行,整体拒绝");
|
|
1103
|
+
let redacted = false;
|
|
1104
|
+
const snapshot = {
|
|
1105
|
+
goal: "",
|
|
1106
|
+
progress: "",
|
|
1107
|
+
next: ""
|
|
1108
|
+
};
|
|
1109
|
+
for (const key of SECTION_ORDER) {
|
|
1110
|
+
const result = redactSensitive(record[key]);
|
|
1111
|
+
snapshot[key] = result.text;
|
|
1112
|
+
redacted = redacted || result.redacted;
|
|
1113
|
+
}
|
|
1114
|
+
for (const key of SECTION_ORDER) if (utf8Bytes(snapshot[key]) > 8192) return bad("field-too-large", "冻结快照字段 " + SECTION_NAMES[key] + " 超过 8 KiB 上限");
|
|
1115
|
+
const extras = {};
|
|
1116
|
+
for (const key of extraKeys) if (key in record) extras[key] = record[key];
|
|
1117
|
+
return {
|
|
1118
|
+
ok: true,
|
|
1119
|
+
snapshot: {
|
|
1120
|
+
goal: snapshot.goal,
|
|
1121
|
+
progress: snapshot.progress,
|
|
1122
|
+
next: snapshot.next
|
|
1123
|
+
},
|
|
1124
|
+
redacted,
|
|
1125
|
+
extras
|
|
1126
|
+
};
|
|
1127
|
+
}
|
|
1128
|
+
/**
|
|
1129
|
+
* Parse the freeze-request format: a <<<FREEZE ... >>>FREEZE block whose body
|
|
1130
|
+
* is 目标: / 进度: / 下一步: section headers, each followed by body lines.
|
|
1131
|
+
* The gate applies before returning: slash-command taint rejects the whole
|
|
1132
|
+
* request, sensitive patterns are redacted to markers, and each field is
|
|
1133
|
+
* capped at FREEZE_FIELD_BYTE_LIMIT UTF-8 bytes.
|
|
1134
|
+
*/
|
|
1135
|
+
function parseFreezeRequest(input) {
|
|
1136
|
+
const beginIndex = input.indexOf(BEGIN);
|
|
1137
|
+
if (beginIndex === -1) return fail("missing-block", "未找到冻结请求块(需要 <<<FREEZE ... >>>FREEZE)");
|
|
1138
|
+
const bodyStart = beginIndex + 9;
|
|
1139
|
+
const endIndex = input.indexOf(END, bodyStart);
|
|
1140
|
+
if (endIndex === -1) return fail("unterminated-block", "冻结请求块未闭合(缺少 >>>FREEZE)");
|
|
1141
|
+
const block = input.slice(bodyStart, endIndex);
|
|
1142
|
+
const raw = /* @__PURE__ */ new Map();
|
|
1143
|
+
let current;
|
|
1144
|
+
for (const line of block.split(/\r?\n/)) {
|
|
1145
|
+
const key = SECTION_KEYS.get(line);
|
|
1146
|
+
if (key !== void 0) {
|
|
1147
|
+
if (raw.has(key)) return fail("duplicate-section", `重复的段落标题:${SECTION_NAMES[key]}`);
|
|
1148
|
+
raw.set(key, []);
|
|
1149
|
+
current = key;
|
|
1150
|
+
} else if (current !== void 0) raw.get(current).push(line);
|
|
1151
|
+
}
|
|
1152
|
+
const snapshot = {
|
|
1153
|
+
goal: "",
|
|
1154
|
+
progress: "",
|
|
1155
|
+
next: ""
|
|
1156
|
+
};
|
|
1157
|
+
for (const key of SECTION_ORDER) {
|
|
1158
|
+
if (!raw.has(key)) return fail("missing-section", `冻结请求缺少段落:${SECTION_NAMES[key]}`);
|
|
1159
|
+
const lines = raw.get(key);
|
|
1160
|
+
while (lines.length > 0 && lines[lines.length - 1].trim() === "") lines.pop();
|
|
1161
|
+
snapshot[key] = lines.join("\n");
|
|
1162
|
+
}
|
|
1163
|
+
for (const key of SECTION_ORDER) if (hasSlashCommandLines(snapshot[key])) return fail("dsh-command-line", `冻结文本的${SECTION_NAMES[key]}包含以 / 开头的命令行,整体拒绝`);
|
|
1164
|
+
let redacted = false;
|
|
1165
|
+
for (const key of SECTION_ORDER) {
|
|
1166
|
+
const result = redactSensitive(snapshot[key]);
|
|
1167
|
+
snapshot[key] = result.text;
|
|
1168
|
+
redacted = redacted || result.redacted;
|
|
1169
|
+
}
|
|
1170
|
+
for (const key of SECTION_ORDER) if (utf8Bytes(snapshot[key]) > 8192) return fail("field-too-large", `冻结快照字段 ${SECTION_NAMES[key]} 超过 8 KiB 上限`);
|
|
1171
|
+
return {
|
|
1172
|
+
ok: true,
|
|
1173
|
+
snapshot: {
|
|
1174
|
+
goal: snapshot.goal,
|
|
1175
|
+
progress: snapshot.progress,
|
|
1176
|
+
next: snapshot.next
|
|
1177
|
+
},
|
|
1178
|
+
warnings: redacted ? ["redacted"] : []
|
|
1179
|
+
};
|
|
1180
|
+
}
|
|
1181
|
+
/** The board's notion of the deployment session-default permission (fail-safe default). */
|
|
1182
|
+
const DEFAULT_SESSION_PERMISSION = "read-only";
|
|
1183
|
+
/** Permission elevation rank (higher = more authority). */
|
|
1184
|
+
const PERMISSION_RANK = /* @__PURE__ */ new Map([
|
|
1185
|
+
["read-only", 0],
|
|
1186
|
+
["workspace-write", 1],
|
|
1187
|
+
["danger-full-access", 2]
|
|
1188
|
+
]);
|
|
1189
|
+
function byteLength(value) {
|
|
1190
|
+
return new TextEncoder().encode(value).length;
|
|
1191
|
+
}
|
|
1192
|
+
/**
|
|
1193
|
+
* Gate a handover bundle from the wire or disk: exact keys, string targets
|
|
1194
|
+
* under the byte cap, a known permission, and a bounded string reference
|
|
1195
|
+
* list. Returns the sanitized bundle, or undefined when rejected.
|
|
1196
|
+
*/
|
|
1197
|
+
function sanitizeHandover(value) {
|
|
1198
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) return void 0;
|
|
1199
|
+
const bundle = value;
|
|
1200
|
+
if (!Object.keys(bundle).every((key) => [
|
|
1201
|
+
"workspaceId",
|
|
1202
|
+
"mode",
|
|
1203
|
+
"permission",
|
|
1204
|
+
"references"
|
|
1205
|
+
].includes(key))) return void 0;
|
|
1206
|
+
if (!Array.isArray(bundle.references)) return void 0;
|
|
1207
|
+
if (bundle.references.length > 32) return void 0;
|
|
1208
|
+
let total = 0;
|
|
1209
|
+
for (const reference of bundle.references) {
|
|
1210
|
+
if (typeof reference !== "string" || reference === "") return void 0;
|
|
1211
|
+
const size = byteLength(reference);
|
|
1212
|
+
if (size > 512) return void 0;
|
|
1213
|
+
total += size;
|
|
1214
|
+
if (total > 8192) return void 0;
|
|
1215
|
+
}
|
|
1216
|
+
for (const key of ["workspaceId", "mode"]) {
|
|
1217
|
+
const target = bundle[key];
|
|
1218
|
+
if (target !== void 0 && (typeof target !== "string" || byteLength(target) > 256)) return void 0;
|
|
1219
|
+
}
|
|
1220
|
+
if (bundle.permission !== void 0 && !isTaskPermission(bundle.permission)) return void 0;
|
|
1221
|
+
const workspaceId = bundle.workspaceId;
|
|
1222
|
+
const mode = bundle.mode;
|
|
1223
|
+
return {
|
|
1224
|
+
...typeof workspaceId === "string" ? { workspaceId } : {},
|
|
1225
|
+
...typeof mode === "string" ? { mode } : {},
|
|
1226
|
+
...bundle.permission === void 0 ? {} : { permission: bundle.permission },
|
|
1227
|
+
references: [...bundle.references]
|
|
1228
|
+
};
|
|
1229
|
+
}
|
|
1230
|
+
/** The permission an execution session would actually run under. */
|
|
1231
|
+
function effectivePermission(task) {
|
|
1232
|
+
return task.handover?.permission ?? task.permission;
|
|
1233
|
+
}
|
|
1234
|
+
/** Whether a binding's permission is elevated above the session default. */
|
|
1235
|
+
function exceedsSessionDefault(permission, sessionDefault) {
|
|
1236
|
+
if (permission === void 0) return false;
|
|
1237
|
+
return (PERMISSION_RANK.get(permission) ?? -1) > (PERMISSION_RANK.get(sessionDefault) ?? -1);
|
|
1238
|
+
}
|
|
1239
|
+
/**
|
|
1240
|
+
* The confirmation-gate predicate: an elevated permission without a human
|
|
1241
|
+
* confirmation stamp. Manual run/rerun and cron must refuse such a card.
|
|
1242
|
+
*/
|
|
1243
|
+
function requiresPermissionConfirmation(task, sessionDefault = DEFAULT_SESSION_PERMISSION) {
|
|
1244
|
+
return exceedsSessionDefault(effectivePermission(task), sessionDefault) && task.permissionConfirmedAt === void 0;
|
|
1245
|
+
}
|
|
958
1246
|
//#endregion
|
|
959
1247
|
//#region src/core/store.ts
|
|
960
1248
|
/**
|
|
@@ -998,6 +1286,9 @@ window.__ModuleLoader__.load({
|
|
|
998
1286
|
if (entry.endedAt !== void 0 && typeof entry.endedAt !== "number") return false;
|
|
999
1287
|
if (entry.result !== void 0 && entry.result !== "succeeded" && entry.result !== "failed" && entry.result !== "cancelled") return false;
|
|
1000
1288
|
if (entry.error !== void 0 && typeof entry.error !== "string") return false;
|
|
1289
|
+
if (entry.initiatedBy !== void 0 && typeof entry.initiatedBy !== "string") return false;
|
|
1290
|
+
if (entry.frozenBy !== void 0 && typeof entry.frozenBy !== "string") return false;
|
|
1291
|
+
if (entry.frozenAt !== void 0 && typeof entry.frozenAt !== "number") return false;
|
|
1001
1292
|
}
|
|
1002
1293
|
return true;
|
|
1003
1294
|
}
|
|
@@ -1022,6 +1313,49 @@ window.__ModuleLoader__.load({
|
|
|
1022
1313
|
lastTriggeredAt: typeof rule.lastTriggeredAt === "number" ? rule.lastTriggeredAt : void 0
|
|
1023
1314
|
};
|
|
1024
1315
|
}
|
|
1316
|
+
/**
|
|
1317
|
+
* Repair a persisted freeze snapshot: shape + gate re-check (slash taint,
|
|
1318
|
+
* redaction idempotence, byte cap); a malformed or tainted snapshot is
|
|
1319
|
+
* dropped (undefined) rather than dropping the whole task row, mirroring
|
|
1320
|
+
* the schedule repair policy.
|
|
1321
|
+
*/
|
|
1322
|
+
function normalizeFreeze(value) {
|
|
1323
|
+
const result = sanitizeFreezeSnapshot(value, [
|
|
1324
|
+
"frozenAt",
|
|
1325
|
+
"redacted",
|
|
1326
|
+
"frozenBy"
|
|
1327
|
+
]);
|
|
1328
|
+
if (!result.ok) return void 0;
|
|
1329
|
+
const frozenAt = result.extras.frozenAt;
|
|
1330
|
+
if (typeof frozenAt !== "number" || !Number.isFinite(frozenAt)) return void 0;
|
|
1331
|
+
if (result.extras.redacted !== void 0 && result.extras.redacted !== true) return void 0;
|
|
1332
|
+
const frozenBy = result.extras.frozenBy;
|
|
1333
|
+
if (frozenBy !== void 0 && (typeof frozenBy !== "string" || frozenBy === "")) return void 0;
|
|
1334
|
+
return {
|
|
1335
|
+
goal: result.snapshot.goal,
|
|
1336
|
+
progress: result.snapshot.progress,
|
|
1337
|
+
next: result.snapshot.next,
|
|
1338
|
+
frozenAt,
|
|
1339
|
+
...result.redacted || result.extras.redacted === true ? { redacted: true } : {},
|
|
1340
|
+
...frozenBy === void 0 ? {} : { frozenBy }
|
|
1341
|
+
};
|
|
1342
|
+
}
|
|
1343
|
+
/**
|
|
1344
|
+
* Repair a persisted handover bundle: shape re-check through the same gate
|
|
1345
|
+
* as the wire path; a malformed bundle is dropped rather than dropping the
|
|
1346
|
+
* task row (mirroring the schedule/freeze repair policy).
|
|
1347
|
+
*/
|
|
1348
|
+
function normalizeHandover(value) {
|
|
1349
|
+
if (typeof value !== "object" || value === null) return void 0;
|
|
1350
|
+
const { bundledAt, ...rest } = value;
|
|
1351
|
+
const bundle = sanitizeHandover(rest);
|
|
1352
|
+
if (bundle === void 0) return void 0;
|
|
1353
|
+
if (typeof bundledAt !== "number" || !Number.isFinite(bundledAt)) return void 0;
|
|
1354
|
+
return {
|
|
1355
|
+
...bundle,
|
|
1356
|
+
bundledAt
|
|
1357
|
+
};
|
|
1358
|
+
}
|
|
1025
1359
|
/** Parse + validate a persisted ledger document; invalid rows are dropped. */
|
|
1026
1360
|
function parseLedger(raw) {
|
|
1027
1361
|
if (raw === null) return [];
|
|
@@ -1051,6 +1385,9 @@ window.__ModuleLoader__.load({
|
|
|
1051
1385
|
task.mode = normalizeTargetId(row.mode);
|
|
1052
1386
|
task.archivedAt = typeof row.archivedAt === "number" && Number.isFinite(row.archivedAt) ? row.archivedAt : void 0;
|
|
1053
1387
|
task.permission = isTaskPermission(row.permission) ? row.permission : void 0;
|
|
1388
|
+
task.freeze = normalizeFreeze(row.freeze);
|
|
1389
|
+
task.handover = normalizeHandover(row.handover);
|
|
1390
|
+
task.permissionConfirmedAt = typeof row.permissionConfirmedAt === "number" && Number.isFinite(row.permissionConfirmedAt) ? row.permissionConfirmedAt : void 0;
|
|
1054
1391
|
tasks.push(task);
|
|
1055
1392
|
}
|
|
1056
1393
|
return tasks;
|
|
@@ -1175,6 +1512,24 @@ window.__ModuleLoader__.load({
|
|
|
1175
1512
|
"new.submit": "创建",
|
|
1176
1513
|
"new.cancel": "取消",
|
|
1177
1514
|
"new.required": "标题不能为空",
|
|
1515
|
+
"new.freeze": "冻结快照(可选)",
|
|
1516
|
+
"new.freezePlaceholder": "粘贴会话输出的 <<<FREEZE … >>>FREEZE 冻结块,解析为目标/进度/下一步(可选)",
|
|
1517
|
+
"detail.freeze": "冻结快照",
|
|
1518
|
+
"detail.freeze.goal": "目标",
|
|
1519
|
+
"detail.freeze.progress": "进度",
|
|
1520
|
+
"detail.freeze.next": "下一步",
|
|
1521
|
+
"detail.freeze.frozenAt": "冻结于 {time}",
|
|
1522
|
+
"detail.freeze.frozenBy": "来源会话 {session}",
|
|
1523
|
+
"detail.freeze.redacted": "冻结文本命中敏感模式,已自动替换为 [REDACTED];接手前请人工复核。",
|
|
1524
|
+
"card.frozen": "冻结",
|
|
1525
|
+
"new.handover": "交接包引用(可选)",
|
|
1526
|
+
"new.handoverPlaceholder": "每行一个文档/脚本引用;填写后,上面选择的钉住三元组(工作区/模式/权限)会作为交接包附加到卡片",
|
|
1527
|
+
"detail.handover": "交接包",
|
|
1528
|
+
"detail.handover.bundledAt": "打包于 {time}",
|
|
1529
|
+
"detail.handover.references": "引用",
|
|
1530
|
+
"detail.permissionPending": "权限待确认:本卡有效权限({permission})高于会话默认,手动执行与定时调度都会被拒绝,需人工确认后才能运行。",
|
|
1531
|
+
"detail.permissionConfirm": "确认权限绑定",
|
|
1532
|
+
"detail.permissionConfirmed": "已确认权限绑定 · {time}",
|
|
1178
1533
|
"detail.title": "任务详情",
|
|
1179
1534
|
"detail.edit": "编辑",
|
|
1180
1535
|
"detail.close": "关闭",
|
|
@@ -1193,6 +1548,7 @@ window.__ModuleLoader__.load({
|
|
|
1193
1548
|
"detail.viewSession": "查看会话",
|
|
1194
1549
|
"detail.noSession": "暂无会话",
|
|
1195
1550
|
"detail.executionStarted": "已启动",
|
|
1551
|
+
"detail.execution.initiator": "发起会话 {session}",
|
|
1196
1552
|
"detail.executionEnded": "已结束",
|
|
1197
1553
|
"detail.result.succeeded": "成功",
|
|
1198
1554
|
"detail.result.failed": "失败",
|
|
@@ -1253,7 +1609,7 @@ window.__ModuleLoader__.load({
|
|
|
1253
1609
|
"settings.off": "关",
|
|
1254
1610
|
"settings.overridden": "已覆盖",
|
|
1255
1611
|
"settings.reset": "恢复默认",
|
|
1256
|
-
"settings.notExposed": "当前 DSH 版本未向设置页暴露本插件的配置命名空间,表单不可用。可编辑 ~/.dsh/settings.yaml
|
|
1612
|
+
"settings.notExposed": "当前 DSH 版本未向设置页暴露本插件的配置命名空间,表单不可用。可编辑 ~/.dsh/settings.yaml 直接配置,或将本命名空间加入 Host 设置白名单后重启。",
|
|
1257
1613
|
"settings.readOnly": "当前部署的设置只读。",
|
|
1258
1614
|
"settings.expand": "展开设置",
|
|
1259
1615
|
"settings.collapse": "收起设置",
|
|
@@ -1299,6 +1655,24 @@ window.__ModuleLoader__.load({
|
|
|
1299
1655
|
"new.submit": "Create",
|
|
1300
1656
|
"new.cancel": "Cancel",
|
|
1301
1657
|
"new.required": "Title is required",
|
|
1658
|
+
"new.freeze": "Frozen snapshot (optional)",
|
|
1659
|
+
"new.freezePlaceholder": "Paste a <<<FREEZE ... >>>FREEZE block from a session; it parses into goal/progress/next (optional)",
|
|
1660
|
+
"detail.freeze": "Frozen snapshot",
|
|
1661
|
+
"detail.freeze.goal": "Goal",
|
|
1662
|
+
"detail.freeze.progress": "Progress",
|
|
1663
|
+
"detail.freeze.next": "Next",
|
|
1664
|
+
"detail.freeze.frozenAt": "Frozen at {time}",
|
|
1665
|
+
"detail.freeze.frozenBy": "Source session {session}",
|
|
1666
|
+
"detail.freeze.redacted": "Sensitive patterns were detected in the frozen text and replaced with [REDACTED]; review manually before resuming.",
|
|
1667
|
+
"card.frozen": "frozen",
|
|
1668
|
+
"new.handover": "Handover references (optional)",
|
|
1669
|
+
"new.handoverPlaceholder": "One doc/script reference per line; when filled, the pinned triplet picked above (workspace/mode/permission) is attached to the card as a handover bundle",
|
|
1670
|
+
"detail.handover": "Handover bundle",
|
|
1671
|
+
"detail.handover.bundledAt": "Bundled at {time}",
|
|
1672
|
+
"detail.handover.references": "References",
|
|
1673
|
+
"detail.permissionPending": "Permission pending confirmation: this card's effective permission ({permission}) is above the session default; manual runs and cron are refused until a human confirms.",
|
|
1674
|
+
"detail.permissionConfirm": "Confirm permission binding",
|
|
1675
|
+
"detail.permissionConfirmed": "Permission confirmed · {time}",
|
|
1302
1676
|
"detail.title": "Task Detail",
|
|
1303
1677
|
"detail.edit": "Edit",
|
|
1304
1678
|
"detail.close": "Close",
|
|
@@ -1317,6 +1691,7 @@ window.__ModuleLoader__.load({
|
|
|
1317
1691
|
"detail.viewSession": "View Session",
|
|
1318
1692
|
"detail.noSession": "No session",
|
|
1319
1693
|
"detail.executionStarted": "Started",
|
|
1694
|
+
"detail.execution.initiator": "Initiated by session {session}",
|
|
1320
1695
|
"detail.executionEnded": "Ended",
|
|
1321
1696
|
"detail.result.succeeded": "Succeeded",
|
|
1322
1697
|
"detail.result.failed": "Failed",
|
|
@@ -1377,7 +1752,7 @@ window.__ModuleLoader__.load({
|
|
|
1377
1752
|
"settings.off": "Off",
|
|
1378
1753
|
"settings.overridden": "Overridden",
|
|
1379
1754
|
"settings.reset": "Reset to default",
|
|
1380
|
-
"settings.notExposed": "This DSH version does not expose this plugin's settings namespace to the configuration page, so the form is unavailable. Edit ~/.dsh/settings.yaml directly, or add the namespace to
|
|
1755
|
+
"settings.notExposed": "This DSH version does not expose this plugin's settings namespace to the configuration page, so the form is unavailable. Edit ~/.dsh/settings.yaml directly, or add the namespace to the Host settings allowlist and restart.",
|
|
1381
1756
|
"settings.readOnly": "This deployment stores settings read-only.",
|
|
1382
1757
|
"settings.expand": "Show settings",
|
|
1383
1758
|
"settings.collapse": "Hide settings",
|
|
@@ -1498,6 +1873,95 @@ window.__ModuleLoader__.load({
|
|
|
1498
1873
|
}
|
|
1499
1874
|
];
|
|
1500
1875
|
//#endregion
|
|
1876
|
+
//#region src/client/board/TaskForm.tsx
|
|
1877
|
+
/** Modal overlay: closes on backdrop press, submits through the form. */
|
|
1878
|
+
function ModalShell({ ariaLabel, title, error, pending, submitLabel, onSubmit, onClose, children }) {
|
|
1879
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1880
|
+
className: board_module_css_default.modalBackdrop,
|
|
1881
|
+
onMouseDown: (event) => {
|
|
1882
|
+
if (event.target === event.currentTarget) onClose();
|
|
1883
|
+
},
|
|
1884
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("form", {
|
|
1885
|
+
className: board_module_css_default.modal,
|
|
1886
|
+
role: "dialog",
|
|
1887
|
+
"aria-label": ariaLabel,
|
|
1888
|
+
onSubmit: (event) => {
|
|
1889
|
+
event.preventDefault();
|
|
1890
|
+
onSubmit();
|
|
1891
|
+
},
|
|
1892
|
+
children: [
|
|
1893
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h2", {
|
|
1894
|
+
className: board_module_css_default.modalTitle,
|
|
1895
|
+
children: title
|
|
1896
|
+
}),
|
|
1897
|
+
children,
|
|
1898
|
+
error !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1899
|
+
className: board_module_css_default.formError,
|
|
1900
|
+
children: error
|
|
1901
|
+
}),
|
|
1902
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("footer", {
|
|
1903
|
+
className: board_module_css_default.modalFooter,
|
|
1904
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1905
|
+
type: "button",
|
|
1906
|
+
className: board_module_css_default.ghostButton,
|
|
1907
|
+
onClick: onClose,
|
|
1908
|
+
children: t("new.cancel")
|
|
1909
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1910
|
+
type: "submit",
|
|
1911
|
+
className: board_module_css_default.primaryButton,
|
|
1912
|
+
disabled: pending,
|
|
1913
|
+
children: submitLabel
|
|
1914
|
+
})]
|
|
1915
|
+
})
|
|
1916
|
+
]
|
|
1917
|
+
})
|
|
1918
|
+
});
|
|
1919
|
+
}
|
|
1920
|
+
/** Title + description + prompt fields shared by the new and edit task forms. */
|
|
1921
|
+
function TaskContentFields({ title, description, prompt, onTitleChange, onDescriptionChange, onPromptChange }) {
|
|
1922
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
|
|
1923
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
1924
|
+
className: board_module_css_default.field,
|
|
1925
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1926
|
+
className: board_module_css_default.fieldLabel,
|
|
1927
|
+
children: t("new.title")
|
|
1928
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
1929
|
+
className: board_module_css_default.input,
|
|
1930
|
+
value: title,
|
|
1931
|
+
autoFocus: true,
|
|
1932
|
+
placeholder: t("new.titlePlaceholder"),
|
|
1933
|
+
onChange: (event) => onTitleChange(event.target.value)
|
|
1934
|
+
})]
|
|
1935
|
+
}),
|
|
1936
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
1937
|
+
className: board_module_css_default.field,
|
|
1938
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1939
|
+
className: board_module_css_default.fieldLabel,
|
|
1940
|
+
children: t("new.description")
|
|
1941
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("textarea", {
|
|
1942
|
+
className: board_module_css_default.input,
|
|
1943
|
+
rows: 3,
|
|
1944
|
+
value: description,
|
|
1945
|
+
placeholder: t("new.descriptionPlaceholder"),
|
|
1946
|
+
onChange: (event) => onDescriptionChange(event.target.value)
|
|
1947
|
+
})]
|
|
1948
|
+
}),
|
|
1949
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
1950
|
+
className: board_module_css_default.field,
|
|
1951
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1952
|
+
className: board_module_css_default.fieldLabel,
|
|
1953
|
+
children: t("new.prompt")
|
|
1954
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("textarea", {
|
|
1955
|
+
className: board_module_css_default.input,
|
|
1956
|
+
rows: 4,
|
|
1957
|
+
value: prompt,
|
|
1958
|
+
placeholder: t("new.promptPlaceholder"),
|
|
1959
|
+
onChange: (event) => onPromptChange(event.target.value)
|
|
1960
|
+
})]
|
|
1961
|
+
})
|
|
1962
|
+
] });
|
|
1963
|
+
}
|
|
1964
|
+
//#endregion
|
|
1501
1965
|
//#region src/client/board/NewTaskModal.tsx
|
|
1502
1966
|
/**
|
|
1503
1967
|
* New-task modal: title + description + the prompt that execution will send.
|
|
@@ -1514,6 +1978,9 @@ window.__ModuleLoader__.load({
|
|
|
1514
1978
|
const [scheduleEnabled, setScheduleEnabled] = (0, react.useState)(false);
|
|
1515
1979
|
const [scheduleCron, setScheduleCron] = (0, react.useState)("");
|
|
1516
1980
|
const [scheduleError, setScheduleError] = (0, react.useState)(void 0);
|
|
1981
|
+
const [freezeText, setFreezeText] = (0, react.useState)("");
|
|
1982
|
+
const [freezeError, setFreezeError] = (0, react.useState)(void 0);
|
|
1983
|
+
const [handoverText, setHandoverText] = (0, react.useState)("");
|
|
1517
1984
|
const [error, setError] = (0, react.useState)(void 0);
|
|
1518
1985
|
const [pending, setPending] = (0, react.useState)(false);
|
|
1519
1986
|
const [options, setOptions] = (0, react.useState)(controller.getSnapshot().executionOptions);
|
|
@@ -1526,11 +1993,32 @@ window.__ModuleLoader__.load({
|
|
|
1526
1993
|
return;
|
|
1527
1994
|
}
|
|
1528
1995
|
}
|
|
1996
|
+
let freeze = void 0;
|
|
1997
|
+
if (freezeText.trim() !== "") {
|
|
1998
|
+
const parsed = parseFreezeRequest(freezeText);
|
|
1999
|
+
if (!parsed.ok) {
|
|
2000
|
+
setFreezeError(parsed.error.message);
|
|
2001
|
+
return;
|
|
2002
|
+
}
|
|
2003
|
+
freeze = {
|
|
2004
|
+
...parsed.snapshot,
|
|
2005
|
+
...parsed.warnings.includes("redacted") ? { redacted: true } : {}
|
|
2006
|
+
};
|
|
2007
|
+
}
|
|
2008
|
+
const references = handoverText.split("\n").map((line) => line.trim()).filter((line) => line !== "");
|
|
2009
|
+
const handover = references.length === 0 ? void 0 : {
|
|
2010
|
+
references,
|
|
2011
|
+
workspaceId: workspaceId === "" ? void 0 : workspaceId,
|
|
2012
|
+
mode: mode === "" ? void 0 : mode,
|
|
2013
|
+
permission: permission === "" ? void 0 : permission
|
|
2014
|
+
};
|
|
1529
2015
|
setPending(true);
|
|
1530
2016
|
if (await controller.createTaskConfirmed({
|
|
1531
2017
|
title,
|
|
1532
2018
|
description,
|
|
1533
2019
|
prompt,
|
|
2020
|
+
freeze,
|
|
2021
|
+
handover,
|
|
1534
2022
|
workspaceId: workspaceId === "" ? void 0 : workspaceId,
|
|
1535
2023
|
mode: mode === "" ? void 0 : mode,
|
|
1536
2024
|
permission: permission === "" ? void 0 : permission,
|
|
@@ -1547,216 +2035,192 @@ window.__ModuleLoader__.load({
|
|
|
1547
2035
|
};
|
|
1548
2036
|
/** Next-run preview for a valid armed cron (creation-time only). */
|
|
1549
2037
|
const scheduleNextRun = scheduleEnabled && scheduleCron.trim() !== "" && isValidCron(scheduleCron) ? nextRunAtMs(scheduleCron, Date.now()) : void 0;
|
|
1550
|
-
return /* @__PURE__ */ (0, react_jsx_runtime.
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
2038
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(ModalShell, {
|
|
2039
|
+
ariaLabel: t("board.new"),
|
|
2040
|
+
title: t("board.new"),
|
|
2041
|
+
error,
|
|
2042
|
+
pending,
|
|
2043
|
+
submitLabel: t("new.submit"),
|
|
2044
|
+
onSubmit: () => {
|
|
2045
|
+
submit();
|
|
1554
2046
|
},
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
}
|
|
1583
|
-
})
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("
|
|
1617
|
-
|
|
1618
|
-
children: t("
|
|
1619
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("
|
|
1637
|
-
|
|
1638
|
-
children: t("
|
|
1639
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("
|
|
1662
|
-
|
|
1663
|
-
children: t("
|
|
1664
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
/* @__PURE__ */ (0, react_jsx_runtime.
|
|
1684
|
-
|
|
2047
|
+
onClose,
|
|
2048
|
+
children: [
|
|
2049
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(TaskContentFields, {
|
|
2050
|
+
title,
|
|
2051
|
+
description,
|
|
2052
|
+
prompt,
|
|
2053
|
+
onTitleChange: (value) => {
|
|
2054
|
+
setTitle(value);
|
|
2055
|
+
setError(void 0);
|
|
2056
|
+
},
|
|
2057
|
+
onDescriptionChange: setDescription,
|
|
2058
|
+
onPromptChange: setPrompt
|
|
2059
|
+
}),
|
|
2060
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
2061
|
+
className: board_module_css_default.field,
|
|
2062
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2063
|
+
className: board_module_css_default.fieldLabel,
|
|
2064
|
+
children: t("new.freeze")
|
|
2065
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("textarea", {
|
|
2066
|
+
className: board_module_css_default.input,
|
|
2067
|
+
rows: 4,
|
|
2068
|
+
value: freezeText,
|
|
2069
|
+
placeholder: t("new.freezePlaceholder"),
|
|
2070
|
+
spellCheck: false,
|
|
2071
|
+
onChange: (event) => {
|
|
2072
|
+
setFreezeText(event.target.value);
|
|
2073
|
+
setFreezeError(void 0);
|
|
2074
|
+
}
|
|
2075
|
+
})]
|
|
2076
|
+
}),
|
|
2077
|
+
freezeError !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
2078
|
+
className: board_module_css_default.formError,
|
|
2079
|
+
children: freezeError
|
|
2080
|
+
}),
|
|
2081
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
2082
|
+
className: board_module_css_default.field,
|
|
2083
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2084
|
+
className: board_module_css_default.fieldLabel,
|
|
2085
|
+
children: t("new.handover")
|
|
2086
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("textarea", {
|
|
2087
|
+
className: board_module_css_default.input,
|
|
2088
|
+
rows: 3,
|
|
2089
|
+
value: handoverText,
|
|
2090
|
+
placeholder: t("new.handoverPlaceholder"),
|
|
2091
|
+
spellCheck: false,
|
|
2092
|
+
onChange: (event) => {
|
|
2093
|
+
setHandoverText(event.target.value);
|
|
2094
|
+
}
|
|
2095
|
+
})]
|
|
2096
|
+
}),
|
|
2097
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
2098
|
+
className: board_module_css_default.field,
|
|
2099
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2100
|
+
className: board_module_css_default.fieldLabel,
|
|
2101
|
+
children: t("new.workspace")
|
|
2102
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
|
|
2103
|
+
className: board_module_css_default.select,
|
|
2104
|
+
value: workspaceId,
|
|
2105
|
+
onChange: (event) => {
|
|
2106
|
+
setWorkspaceId(event.target.value);
|
|
2107
|
+
},
|
|
2108
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
2109
|
+
value: "",
|
|
2110
|
+
children: t("exec.workspace.recent")
|
|
2111
|
+
}), options.workspaces.map((workspace) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
2112
|
+
value: workspace.workspaceId,
|
|
2113
|
+
children: workspace.title
|
|
2114
|
+
}, workspace.workspaceId))]
|
|
2115
|
+
})]
|
|
2116
|
+
}),
|
|
2117
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
2118
|
+
className: board_module_css_default.field,
|
|
2119
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2120
|
+
className: board_module_css_default.fieldLabel,
|
|
2121
|
+
children: t("new.mode")
|
|
2122
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
|
|
2123
|
+
className: board_module_css_default.select,
|
|
2124
|
+
value: mode,
|
|
2125
|
+
onChange: (event) => {
|
|
2126
|
+
setMode(event.target.value);
|
|
2127
|
+
},
|
|
2128
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
2129
|
+
value: "",
|
|
2130
|
+
children: t("exec.mode.default")
|
|
2131
|
+
}), options.presets.map((preset) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("option", {
|
|
2132
|
+
value: preset.id,
|
|
2133
|
+
disabled: preset.broken !== void 0,
|
|
2134
|
+
children: [
|
|
2135
|
+
preset.name ?? preset.id,
|
|
2136
|
+
preset.isDefault ? t("exec.mode.defaultSuffix") : "",
|
|
2137
|
+
preset.broken !== void 0 ? t("exec.mode.brokenSuffix") : ""
|
|
2138
|
+
]
|
|
2139
|
+
}, preset.id))]
|
|
2140
|
+
})]
|
|
2141
|
+
}),
|
|
2142
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
2143
|
+
className: board_module_css_default.field,
|
|
2144
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2145
|
+
className: board_module_css_default.fieldLabel,
|
|
2146
|
+
children: t("new.permission")
|
|
2147
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
|
|
2148
|
+
className: board_module_css_default.select,
|
|
2149
|
+
value: permission,
|
|
2150
|
+
onChange: (event) => {
|
|
2151
|
+
setPermission(event.target.value);
|
|
2152
|
+
},
|
|
2153
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
2154
|
+
value: "",
|
|
2155
|
+
children: t("exec.permission.default")
|
|
2156
|
+
}), TASK_PERMISSIONS.map((id) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
2157
|
+
value: id,
|
|
2158
|
+
children: t(`exec.permission.${id}`)
|
|
2159
|
+
}, id))]
|
|
2160
|
+
})]
|
|
2161
|
+
}),
|
|
2162
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
|
|
2163
|
+
className: board_module_css_default.detailSection,
|
|
2164
|
+
children: [
|
|
2165
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h4", { children: t("detail.schedule") }),
|
|
2166
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
2167
|
+
className: board_module_css_default.scheduleToggle,
|
|
2168
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
2169
|
+
type: "checkbox",
|
|
2170
|
+
checked: scheduleEnabled,
|
|
2171
|
+
onChange: (event) => {
|
|
2172
|
+
setScheduleEnabled(event.target.checked);
|
|
2173
|
+
if (!event.target.checked) setScheduleError(void 0);
|
|
2174
|
+
}
|
|
2175
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("detail.schedule.enable") })]
|
|
2176
|
+
}),
|
|
2177
|
+
scheduleEnabled && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
|
|
2178
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2179
|
+
className: board_module_css_default.scheduleRow,
|
|
1685
2180
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
1686
|
-
|
|
1687
|
-
|
|
2181
|
+
className: `${board_module_css_default.input} ${board_module_css_default.scheduleInput}${scheduleError !== void 0 ? ` ${board_module_css_default.scheduleInputInvalid}` : ""}`,
|
|
2182
|
+
value: scheduleCron,
|
|
2183
|
+
placeholder: "0 9 * * *",
|
|
2184
|
+
spellCheck: false,
|
|
2185
|
+
"aria-label": t("detail.schedule.cron"),
|
|
1688
2186
|
onChange: (event) => {
|
|
1689
|
-
|
|
1690
|
-
|
|
2187
|
+
setScheduleCron(event.target.value);
|
|
2188
|
+
setScheduleError(void 0);
|
|
1691
2189
|
}
|
|
1692
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
"aria-label": t("detail.schedule.cron"),
|
|
1703
|
-
onChange: (event) => {
|
|
1704
|
-
setScheduleCron(event.target.value);
|
|
1705
|
-
setScheduleError(void 0);
|
|
1706
|
-
}
|
|
1707
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
|
|
1708
|
-
className: board_module_css_default.schedulePreset,
|
|
2190
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
|
|
2191
|
+
className: board_module_css_default.schedulePreset,
|
|
2192
|
+
value: "",
|
|
2193
|
+
"aria-label": t("detail.schedule.presets"),
|
|
2194
|
+
onChange: (event) => {
|
|
2195
|
+
if (event.target.value === "") return;
|
|
2196
|
+
setScheduleCron(event.target.value);
|
|
2197
|
+
setScheduleError(void 0);
|
|
2198
|
+
},
|
|
2199
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("option", {
|
|
1709
2200
|
value: "",
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
" ",
|
|
1734
|
-
new Date(scheduleNextRun).toLocaleString()
|
|
1735
|
-
]
|
|
1736
|
-
})
|
|
1737
|
-
] })
|
|
1738
|
-
]
|
|
1739
|
-
}),
|
|
1740
|
-
error !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1741
|
-
className: board_module_css_default.formError,
|
|
1742
|
-
children: error
|
|
1743
|
-
}),
|
|
1744
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("footer", {
|
|
1745
|
-
className: board_module_css_default.modalFooter,
|
|
1746
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1747
|
-
type: "button",
|
|
1748
|
-
className: board_module_css_default.ghostButton,
|
|
1749
|
-
onClick: onClose,
|
|
1750
|
-
children: t("new.cancel")
|
|
1751
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1752
|
-
type: "submit",
|
|
1753
|
-
className: board_module_css_default.primaryButton,
|
|
1754
|
-
disabled: pending,
|
|
1755
|
-
children: t("new.submit")
|
|
1756
|
-
})]
|
|
1757
|
-
})
|
|
1758
|
-
]
|
|
1759
|
-
})
|
|
2201
|
+
children: [t("detail.schedule.presets"), "…"]
|
|
2202
|
+
}), SCHEDULE_PRESETS.map((preset) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
2203
|
+
value: preset.cron,
|
|
2204
|
+
children: t(preset.label)
|
|
2205
|
+
}, preset.cron))]
|
|
2206
|
+
})]
|
|
2207
|
+
}),
|
|
2208
|
+
scheduleError !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
2209
|
+
className: board_module_css_default.formError,
|
|
2210
|
+
children: scheduleError
|
|
2211
|
+
}),
|
|
2212
|
+
scheduleError === void 0 && scheduleNextRun !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
|
|
2213
|
+
className: board_module_css_default.scheduleMeta,
|
|
2214
|
+
children: [
|
|
2215
|
+
t("detail.schedule.nextRun"),
|
|
2216
|
+
" ",
|
|
2217
|
+
new Date(scheduleNextRun).toLocaleString()
|
|
2218
|
+
]
|
|
2219
|
+
})
|
|
2220
|
+
] })
|
|
2221
|
+
]
|
|
2222
|
+
})
|
|
2223
|
+
]
|
|
1760
2224
|
});
|
|
1761
2225
|
}
|
|
1762
2226
|
//#endregion
|
|
@@ -1839,6 +2303,11 @@ window.__ModuleLoader__.load({
|
|
|
1839
2303
|
formatTime(task.updatedAt)
|
|
1840
2304
|
]
|
|
1841
2305
|
}),
|
|
2306
|
+
task.freeze !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2307
|
+
className: board_module_css_default.cardSchedule,
|
|
2308
|
+
title: task.freeze.goal,
|
|
2309
|
+
children: t("card.frozen")
|
|
2310
|
+
}),
|
|
1842
2311
|
!archived && task.schedule?.enabled === true && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1843
2312
|
className: board_module_css_default.cardSchedule,
|
|
1844
2313
|
title: task.schedule.nextRunAt !== void 0 ? `${t("card.scheduled")} · ${formatHostTimestamp(task.schedule.nextRunAt, timeZone)}` : t("card.scheduled"),
|
|
@@ -1953,89 +2422,26 @@ window.__ModuleLoader__.load({
|
|
|
1953
2422
|
setPending(false);
|
|
1954
2423
|
setError(controller.getSnapshot().transportError ?? t("new.required"));
|
|
1955
2424
|
};
|
|
1956
|
-
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(
|
|
1957
|
-
|
|
1958
|
-
|
|
1959
|
-
|
|
2425
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ModalShell, {
|
|
2426
|
+
ariaLabel: t("edit.title"),
|
|
2427
|
+
title: t("edit.title"),
|
|
2428
|
+
error,
|
|
2429
|
+
pending,
|
|
2430
|
+
submitLabel: t("edit.save"),
|
|
2431
|
+
onSubmit: () => {
|
|
2432
|
+
submit();
|
|
1960
2433
|
},
|
|
1961
|
-
|
|
1962
|
-
|
|
1963
|
-
|
|
1964
|
-
|
|
1965
|
-
|
|
1966
|
-
|
|
1967
|
-
|
|
2434
|
+
onClose,
|
|
2435
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(TaskContentFields, {
|
|
2436
|
+
title,
|
|
2437
|
+
description,
|
|
2438
|
+
prompt,
|
|
2439
|
+
onTitleChange: (value) => {
|
|
2440
|
+
setTitle(value);
|
|
2441
|
+
setError(void 0);
|
|
1968
2442
|
},
|
|
1969
|
-
|
|
1970
|
-
|
|
1971
|
-
className: board_module_css_default.modalTitle,
|
|
1972
|
-
children: t("edit.title")
|
|
1973
|
-
}),
|
|
1974
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
1975
|
-
className: board_module_css_default.field,
|
|
1976
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1977
|
-
className: board_module_css_default.fieldLabel,
|
|
1978
|
-
children: t("new.title")
|
|
1979
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
1980
|
-
className: board_module_css_default.input,
|
|
1981
|
-
value: title,
|
|
1982
|
-
autoFocus: true,
|
|
1983
|
-
placeholder: t("new.titlePlaceholder"),
|
|
1984
|
-
onChange: (event) => {
|
|
1985
|
-
setTitle(event.target.value);
|
|
1986
|
-
setError(void 0);
|
|
1987
|
-
}
|
|
1988
|
-
})]
|
|
1989
|
-
}),
|
|
1990
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
1991
|
-
className: board_module_css_default.field,
|
|
1992
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1993
|
-
className: board_module_css_default.fieldLabel,
|
|
1994
|
-
children: t("new.description")
|
|
1995
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("textarea", {
|
|
1996
|
-
className: board_module_css_default.input,
|
|
1997
|
-
rows: 3,
|
|
1998
|
-
value: description,
|
|
1999
|
-
placeholder: t("new.descriptionPlaceholder"),
|
|
2000
|
-
onChange: (event) => {
|
|
2001
|
-
setDescription(event.target.value);
|
|
2002
|
-
}
|
|
2003
|
-
})]
|
|
2004
|
-
}),
|
|
2005
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
2006
|
-
className: board_module_css_default.field,
|
|
2007
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2008
|
-
className: board_module_css_default.fieldLabel,
|
|
2009
|
-
children: t("new.prompt")
|
|
2010
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("textarea", {
|
|
2011
|
-
className: board_module_css_default.input,
|
|
2012
|
-
rows: 4,
|
|
2013
|
-
value: prompt,
|
|
2014
|
-
placeholder: t("new.promptPlaceholder"),
|
|
2015
|
-
onChange: (event) => {
|
|
2016
|
-
setPrompt(event.target.value);
|
|
2017
|
-
}
|
|
2018
|
-
})]
|
|
2019
|
-
}),
|
|
2020
|
-
error !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
2021
|
-
className: board_module_css_default.formError,
|
|
2022
|
-
children: error
|
|
2023
|
-
}),
|
|
2024
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("footer", {
|
|
2025
|
-
className: board_module_css_default.modalFooter,
|
|
2026
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
2027
|
-
type: "button",
|
|
2028
|
-
className: board_module_css_default.ghostButton,
|
|
2029
|
-
onClick: onClose,
|
|
2030
|
-
children: t("new.cancel")
|
|
2031
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
2032
|
-
type: "submit",
|
|
2033
|
-
className: board_module_css_default.primaryButton,
|
|
2034
|
-
disabled: pending,
|
|
2035
|
-
children: t("edit.save")
|
|
2036
|
-
})]
|
|
2037
|
-
})
|
|
2038
|
-
]
|
|
2443
|
+
onDescriptionChange: setDescription,
|
|
2444
|
+
onPromptChange: setPrompt
|
|
2039
2445
|
})
|
|
2040
2446
|
});
|
|
2041
2447
|
}
|
|
@@ -2074,6 +2480,11 @@ window.__ModuleLoader__.load({
|
|
|
2074
2480
|
execution.endedAt !== void 0 && ` · ${t("detail.executionEnded")} ${formatTime(execution.endedAt, timeZone)}`
|
|
2075
2481
|
]
|
|
2076
2482
|
}),
|
|
2483
|
+
execution.initiatedBy !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2484
|
+
className: board_module_css_default.executionTimes,
|
|
2485
|
+
title: execution.initiatedBy,
|
|
2486
|
+
children: t("detail.execution.initiator", { session: execution.initiatedBy })
|
|
2487
|
+
}),
|
|
2077
2488
|
execution.sessionId !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
2078
2489
|
type: "button",
|
|
2079
2490
|
className: board_module_css_default.linkButton,
|
|
@@ -2334,6 +2745,7 @@ window.__ModuleLoader__.load({
|
|
|
2334
2745
|
const pending = snapshot.pendingTaskIds.includes(current.id);
|
|
2335
2746
|
const transportError = snapshot.transportError;
|
|
2336
2747
|
const timeZone = snapshot.host?.scheduler.timeZone;
|
|
2748
|
+
const permissionPending = requiresPermissionConfirmation(current, snapshot.host?.sessionDefaultPermission);
|
|
2337
2749
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2338
2750
|
className: board_module_css_default.modalBackdrop,
|
|
2339
2751
|
onMouseDown: (event) => {
|
|
@@ -2393,6 +2805,107 @@ window.__ModuleLoader__.load({
|
|
|
2393
2805
|
children: current.description !== "" ? current.description : "—"
|
|
2394
2806
|
})]
|
|
2395
2807
|
}),
|
|
2808
|
+
current.freeze !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
|
|
2809
|
+
className: board_module_css_default.detailSection,
|
|
2810
|
+
"data-dsh-part": "freeze",
|
|
2811
|
+
children: [
|
|
2812
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h4", { children: t("detail.freeze") }),
|
|
2813
|
+
current.freeze.redacted === true && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
2814
|
+
className: board_module_css_default.formError,
|
|
2815
|
+
children: t("detail.freeze.redacted")
|
|
2816
|
+
}),
|
|
2817
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
2818
|
+
className: board_module_css_default.detailText,
|
|
2819
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", { children: t("detail.freeze.goal") })
|
|
2820
|
+
}),
|
|
2821
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("pre", {
|
|
2822
|
+
className: board_module_css_default.promptBlock,
|
|
2823
|
+
children: current.freeze.goal
|
|
2824
|
+
}),
|
|
2825
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
2826
|
+
className: board_module_css_default.detailText,
|
|
2827
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", { children: t("detail.freeze.progress") })
|
|
2828
|
+
}),
|
|
2829
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("pre", {
|
|
2830
|
+
className: board_module_css_default.promptBlock,
|
|
2831
|
+
children: current.freeze.progress
|
|
2832
|
+
}),
|
|
2833
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
2834
|
+
className: board_module_css_default.detailText,
|
|
2835
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", { children: t("detail.freeze.next") })
|
|
2836
|
+
}),
|
|
2837
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("pre", {
|
|
2838
|
+
className: board_module_css_default.promptBlock,
|
|
2839
|
+
children: current.freeze.next
|
|
2840
|
+
}),
|
|
2841
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
2842
|
+
className: board_module_css_default.detailMeta,
|
|
2843
|
+
children: t("detail.freeze.frozenAt", { time: formatHostTimestamp(current.freeze.frozenAt, timeZone) })
|
|
2844
|
+
}),
|
|
2845
|
+
current.freeze.frozenBy !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
2846
|
+
className: board_module_css_default.detailMeta,
|
|
2847
|
+
children: t("detail.freeze.frozenBy", { session: current.freeze.frozenBy })
|
|
2848
|
+
})
|
|
2849
|
+
]
|
|
2850
|
+
}),
|
|
2851
|
+
current.handover !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
|
|
2852
|
+
className: board_module_css_default.detailSection,
|
|
2853
|
+
"data-dsh-part": "handover",
|
|
2854
|
+
children: [
|
|
2855
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h4", { children: t("detail.handover") }),
|
|
2856
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
|
|
2857
|
+
className: board_module_css_default.detailText,
|
|
2858
|
+
children: [
|
|
2859
|
+
t("new.workspace"),
|
|
2860
|
+
": ",
|
|
2861
|
+
current.handover.workspaceId ?? t("exec.workspace.recent"),
|
|
2862
|
+
" · ",
|
|
2863
|
+
t("new.mode"),
|
|
2864
|
+
": ",
|
|
2865
|
+
current.handover.mode ?? t("exec.mode.default"),
|
|
2866
|
+
" · ",
|
|
2867
|
+
t("new.permission"),
|
|
2868
|
+
": ",
|
|
2869
|
+
current.handover.permission === void 0 ? t("exec.permission.default") : t(`exec.permission.${current.handover.permission}`)
|
|
2870
|
+
]
|
|
2871
|
+
}),
|
|
2872
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
2873
|
+
className: board_module_css_default.detailText,
|
|
2874
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", { children: t("detail.handover.references") })
|
|
2875
|
+
}),
|
|
2876
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", {
|
|
2877
|
+
className: board_module_css_default.executionList,
|
|
2878
|
+
children: current.handover.references.map((reference) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("li", {
|
|
2879
|
+
className: board_module_css_default.executionRow,
|
|
2880
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("code", { children: reference })
|
|
2881
|
+
}, reference))
|
|
2882
|
+
}),
|
|
2883
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
2884
|
+
className: board_module_css_default.detailMeta,
|
|
2885
|
+
children: t("detail.handover.bundledAt", { time: formatHostTimestamp(current.handover.bundledAt, timeZone) })
|
|
2886
|
+
})
|
|
2887
|
+
]
|
|
2888
|
+
}),
|
|
2889
|
+
permissionPending && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
|
|
2890
|
+
className: board_module_css_default.detailSection,
|
|
2891
|
+
"data-dsh-part": "permission-gate",
|
|
2892
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
2893
|
+
className: board_module_css_default.formError,
|
|
2894
|
+
children: t("detail.permissionPending", { permission: t(`exec.permission.${current.handover?.permission ?? current.permission}`) })
|
|
2895
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
2896
|
+
type: "button",
|
|
2897
|
+
className: board_module_css_default.primaryButton,
|
|
2898
|
+
disabled: pending,
|
|
2899
|
+
onClick: () => {
|
|
2900
|
+
controller.confirmPermission(current.id);
|
|
2901
|
+
},
|
|
2902
|
+
children: t("detail.permissionConfirm")
|
|
2903
|
+
})]
|
|
2904
|
+
}),
|
|
2905
|
+
current.permissionConfirmedAt !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
2906
|
+
className: board_module_css_default.detailMeta,
|
|
2907
|
+
children: t("detail.permissionConfirmed", { time: formatHostTimestamp(current.permissionConfirmedAt, timeZone) })
|
|
2908
|
+
}),
|
|
2396
2909
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
|
|
2397
2910
|
className: board_module_css_default.detailSection,
|
|
2398
2911
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h4", { children: t("detail.prompt") }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("pre", {
|
|
@@ -2538,11 +3051,13 @@ window.__ModuleLoader__.load({
|
|
|
2538
3051
|
* active. Cards open the task detail (never execute directly); the header
|
|
2539
3052
|
* offers filter, new-task, and a back-to-chat escape.
|
|
2540
3053
|
*/
|
|
2541
|
-
/** Case-insensitive title/description match. */
|
|
3054
|
+
/** Case-insensitive title/description/freeze-snapshot match. */
|
|
2542
3055
|
function matchesFilter(task, filter) {
|
|
2543
3056
|
if (filter.trim() === "") return true;
|
|
2544
3057
|
const needle = filter.trim().toLowerCase();
|
|
2545
|
-
|
|
3058
|
+
const haystacks = [task.title, task.description];
|
|
3059
|
+
if (task.freeze !== void 0) haystacks.push(task.freeze.goal, task.freeze.progress, task.freeze.next);
|
|
3060
|
+
return haystacks.some((text) => text.toLowerCase().includes(needle));
|
|
2546
3061
|
}
|
|
2547
3062
|
/**
|
|
2548
3063
|
* Memoized per-card adapter: with a stable `onOpen` from the board and an
|
|
@@ -2774,7 +3289,13 @@ window.__ModuleLoader__.load({
|
|
|
2774
3289
|
let root;
|
|
2775
3290
|
let container;
|
|
2776
3291
|
const ensure = () => {
|
|
2777
|
-
if (container !== void 0)
|
|
3292
|
+
if (container !== void 0) {
|
|
3293
|
+
if (container.isConnected) return;
|
|
3294
|
+
root?.unmount();
|
|
3295
|
+
root = void 0;
|
|
3296
|
+
container.remove();
|
|
3297
|
+
container = void 0;
|
|
3298
|
+
}
|
|
2778
3299
|
const column = conversationColumn();
|
|
2779
3300
|
if (column === void 0) return;
|
|
2780
3301
|
container = document.createElement("div");
|
|
@@ -3370,6 +3891,19 @@ window.__ModuleLoader__.load({
|
|
|
3370
3891
|
});
|
|
3371
3892
|
}
|
|
3372
3893
|
//#endregion
|
|
3894
|
+
//#region \0dsh-store-engine
|
|
3895
|
+
const platform = ["@deepseek-ai/dsh-client", "-store"].join("");
|
|
3896
|
+
const legacy = ["@deepseek-ai/dsh-client-runtime", "/client"].join("");
|
|
3897
|
+
let engine;
|
|
3898
|
+
try {
|
|
3899
|
+
engine = require(platform);
|
|
3900
|
+
} catch {
|
|
3901
|
+
engine = require(legacy);
|
|
3902
|
+
}
|
|
3903
|
+
const createSnapshotStore = engine.createSnapshotStore;
|
|
3904
|
+
engine.defineStore;
|
|
3905
|
+
engine.shallowEqual;
|
|
3906
|
+
//#endregion
|
|
3373
3907
|
//#region src/client/settings-form.ts
|
|
3374
3908
|
/** A boolean field, edited through true/false draft text. */
|
|
3375
3909
|
function booleanField(field) {
|
|
@@ -3429,7 +3963,7 @@ window.__ModuleLoader__.load({
|
|
|
3429
3963
|
}
|
|
3430
3964
|
/** Publish a projection of this form, rebuilt whenever the scope or a draft changes. */
|
|
3431
3965
|
bind(project) {
|
|
3432
|
-
const store =
|
|
3966
|
+
const store = createSnapshotStore(project());
|
|
3433
3967
|
this.listeners.add(() => {
|
|
3434
3968
|
store.set(project());
|
|
3435
3969
|
});
|
|
@@ -3444,7 +3978,7 @@ window.__ModuleLoader__.load({
|
|
|
3444
3978
|
exposed: snapshot.status === "ready",
|
|
3445
3979
|
writable: snapshot.writable,
|
|
3446
3980
|
dirty: plan.length > 0,
|
|
3447
|
-
invalid: plan.some((item) => item.
|
|
3981
|
+
invalid: plan.some((item) => item.judge === void 0),
|
|
3448
3982
|
saving: this.saving,
|
|
3449
3983
|
failed: this.failed,
|
|
3450
3984
|
...this.failedReason === void 0 ? {} : { failedReason: this.failedReason }
|
|
@@ -3494,45 +4028,52 @@ window.__ModuleLoader__.load({
|
|
|
3494
4028
|
};
|
|
3495
4029
|
}
|
|
3496
4030
|
/**
|
|
3497
|
-
* Write every staged edit, then re-seed from
|
|
4031
|
+
* Write every staged edit in one atomic scope mutation, then re-seed from
|
|
4032
|
+
* what the Host accepted.
|
|
3498
4033
|
*
|
|
3499
|
-
*
|
|
3500
|
-
*
|
|
3501
|
-
*
|
|
3502
|
-
*
|
|
3503
|
-
*
|
|
3504
|
-
*
|
|
3505
|
-
*
|
|
4034
|
+
* The whole batch rides one mutate, so cross-field validate hooks
|
|
4035
|
+
* (baseURL+model) judge it as a unit: the Host either applies every write
|
|
4036
|
+
* or refuses the batch. The 0.1.2 scope contract never rejects a refused
|
|
4037
|
+
* mutation — the scope recovers with a fresh Host view and resolves — so
|
|
4038
|
+
* resolution alone proves nothing: the outcome is judged by reading the
|
|
4039
|
+
* settled snapshot back, one planned write at a time, and one missed write
|
|
4040
|
+
* fails the whole save. A scope that still rejects on refusal (the dsh-web
|
|
4041
|
+
* bridge scope) reports through the same failure path with its rejection
|
|
4042
|
+
* message. A save that did not land keeps its drafts, so the user can
|
|
4043
|
+
* correct them instead of retyping.
|
|
4044
|
+
* @returns settlement after the mutation and the read-back.
|
|
3506
4045
|
*/
|
|
3507
4046
|
async save() {
|
|
3508
4047
|
const plan = this.plan();
|
|
3509
|
-
const valid = plan.filter((item) => item.
|
|
4048
|
+
const valid = plan.filter((item) => item.judge !== void 0);
|
|
3510
4049
|
if (plan.length === 0 || this.saving || valid.length !== plan.length) return;
|
|
3511
|
-
const plannedWrites = valid.map((item) => item.op);
|
|
3512
4050
|
const pending = /* @__PURE__ */ new Map();
|
|
3513
4051
|
for (const item of plan) pending.set(item.field, this.staged.get(item.field));
|
|
3514
4052
|
this.saving = true;
|
|
3515
4053
|
this.failed = false;
|
|
3516
4054
|
this.failedReason = void 0;
|
|
3517
4055
|
this.publish();
|
|
3518
|
-
const
|
|
3519
|
-
|
|
3520
|
-
|
|
3521
|
-
|
|
3522
|
-
|
|
3523
|
-
|
|
3524
|
-
|
|
3525
|
-
}
|
|
3526
|
-
|
|
4056
|
+
const ops = valid.map((item) => item.op.op === "set" ? {
|
|
4057
|
+
op: "set",
|
|
4058
|
+
path: [item.field],
|
|
4059
|
+
value: item.op.value
|
|
4060
|
+
} : {
|
|
4061
|
+
op: "unset",
|
|
4062
|
+
path: [item.field]
|
|
4063
|
+
});
|
|
4064
|
+
let failedReason;
|
|
4065
|
+
try {
|
|
4066
|
+
await this.scope.mutate(ops);
|
|
4067
|
+
} catch (error) {
|
|
4068
|
+
failedReason = error instanceof Error ? error.message : String(error);
|
|
4069
|
+
}
|
|
4070
|
+
const landed = failedReason === void 0 && valid.every((item) => item.judge());
|
|
4071
|
+
for (const [field, before] of pending) if (landed && this.staged.get(field) === before) this.staged.delete(field);
|
|
3527
4072
|
this.saving = false;
|
|
3528
|
-
this.failed = landed
|
|
4073
|
+
this.failed = !landed;
|
|
4074
|
+
this.failedReason = failedReason;
|
|
3529
4075
|
this.publish();
|
|
3530
4076
|
}
|
|
3531
|
-
/** The scope's batch surface when it supports one; undefined conservatively otherwise. */
|
|
3532
|
-
batchedScope() {
|
|
3533
|
-
const candidate = this.scope;
|
|
3534
|
-
return typeof candidate?.mutate === "function" ? candidate : void 0;
|
|
3535
|
-
}
|
|
3536
4077
|
/**
|
|
3537
4078
|
* Every staged edit a save would write. An entry whose draft is not a value
|
|
3538
4079
|
* its field accepts carries no write: the form is still dirty, and the save
|
|
@@ -3551,7 +4092,7 @@ window.__ModuleLoader__.load({
|
|
|
3551
4092
|
field,
|
|
3552
4093
|
op: "unset"
|
|
3553
4094
|
},
|
|
3554
|
-
|
|
4095
|
+
judge: () => this.landedUnset(field)
|
|
3555
4096
|
});
|
|
3556
4097
|
continue;
|
|
3557
4098
|
}
|
|
@@ -3563,7 +4104,7 @@ window.__ModuleLoader__.load({
|
|
|
3563
4104
|
field,
|
|
3564
4105
|
op: "unset"
|
|
3565
4106
|
},
|
|
3566
|
-
|
|
4107
|
+
judge: void 0
|
|
3567
4108
|
});
|
|
3568
4109
|
else if (write.kind === "clear") plan.push({
|
|
3569
4110
|
field,
|
|
@@ -3571,7 +4112,7 @@ window.__ModuleLoader__.load({
|
|
|
3571
4112
|
field,
|
|
3572
4113
|
op: "unset"
|
|
3573
4114
|
},
|
|
3574
|
-
|
|
4115
|
+
judge: () => this.landedUnset(field)
|
|
3575
4116
|
});
|
|
3576
4117
|
else plan.push({
|
|
3577
4118
|
field,
|
|
@@ -3580,20 +4121,26 @@ window.__ModuleLoader__.load({
|
|
|
3580
4121
|
op: "set",
|
|
3581
4122
|
value: write.value
|
|
3582
4123
|
},
|
|
3583
|
-
|
|
4124
|
+
judge: () => this.landedSet(field, write.value)
|
|
3584
4125
|
});
|
|
3585
4126
|
}
|
|
3586
4127
|
return plan;
|
|
3587
4128
|
}
|
|
3588
|
-
|
|
3589
|
-
|
|
3590
|
-
|
|
3591
|
-
|
|
3592
|
-
|
|
3593
|
-
await this.scope.set(field, value);
|
|
4129
|
+
/**
|
|
4130
|
+
* Read-back judgment for a planned set: the user layer must hold the
|
|
4131
|
+
* intended value once the mutation has settled.
|
|
4132
|
+
*/
|
|
4133
|
+
landedSet(field, value) {
|
|
3594
4134
|
if (this.specOf(field).secret) return true;
|
|
3595
4135
|
return this.userLayer()?.[field] === value;
|
|
3596
4136
|
}
|
|
4137
|
+
/**
|
|
4138
|
+
* Read-back judgment for a planned unset: the field must be gone from the
|
|
4139
|
+
* user layer once the mutation has settled.
|
|
4140
|
+
*/
|
|
4141
|
+
landedUnset(field) {
|
|
4142
|
+
return !this.stored(field);
|
|
4143
|
+
}
|
|
3597
4144
|
stage(field, edit) {
|
|
3598
4145
|
this.staged.set(field, edit);
|
|
3599
4146
|
this.failed = false;
|
|
@@ -3813,13 +4360,14 @@ window.__ModuleLoader__.load({
|
|
|
3813
4360
|
async state() {
|
|
3814
4361
|
return await this.request(`${TASK_BOARD_API_PREFIX}/state`, { cache: "no-store" });
|
|
3815
4362
|
}
|
|
3816
|
-
async action(action) {
|
|
3817
|
-
return await this.post(uuid(), action);
|
|
4363
|
+
async action(action, initiator) {
|
|
4364
|
+
return await this.post(uuid(), action, initiator);
|
|
3818
4365
|
}
|
|
3819
|
-
async post(requestId, action) {
|
|
4366
|
+
async post(requestId, action, initiator) {
|
|
3820
4367
|
const envelope = {
|
|
3821
4368
|
requestId,
|
|
3822
|
-
action
|
|
4369
|
+
action,
|
|
4370
|
+
...initiator === void 0 || initiator === "" ? {} : { initiator }
|
|
3823
4371
|
};
|
|
3824
4372
|
return await this.request(`${TASK_BOARD_API_PREFIX}/action`, {
|
|
3825
4373
|
method: "POST",
|
|
@@ -3873,7 +4421,7 @@ window.__ModuleLoader__.load({
|
|
|
3873
4421
|
/** The building package's version, when the bundle carries it. */
|
|
3874
4422
|
function bakedVersion() {
|
|
3875
4423
|
try {
|
|
3876
|
-
return "0.3.
|
|
4424
|
+
return "0.3.9";
|
|
3877
4425
|
} catch {
|
|
3878
4426
|
return;
|
|
3879
4427
|
}
|
|
@@ -3941,7 +4489,14 @@ window.__ModuleLoader__.load({
|
|
|
3941
4489
|
const NS = "task-board";
|
|
3942
4490
|
/** Settings namespace the settings card edits (the Host plugin registers it). */
|
|
3943
4491
|
const TASK_BOARD_NS = "task-board";
|
|
3944
|
-
/**
|
|
4492
|
+
/**
|
|
4493
|
+
* Required services (fiber inject waiting — the runtime must be up first).
|
|
4494
|
+
* The generated remote faces are probed at use time instead of injected:
|
|
4495
|
+
* `remote.agentPresets` only registers on 0.1.2-alpha.2 hosts (the
|
|
4496
|
+
* api-remotes contribution), so a hard wait would pend the entry forever
|
|
4497
|
+
* on hosts below that cohort, which serve the same roster through the
|
|
4498
|
+
* connection RPC face.
|
|
4499
|
+
*/
|
|
3945
4500
|
const inject = [
|
|
3946
4501
|
"slots",
|
|
3947
4502
|
"sessions",
|
|
@@ -3952,6 +4507,44 @@ window.__ModuleLoader__.load({
|
|
|
3952
4507
|
"remote"
|
|
3953
4508
|
];
|
|
3954
4509
|
/**
|
|
4510
|
+
* Read the agent-preset roster through whichever face the running host
|
|
4511
|
+
* serves: the generated api-remotes face (`remote.agentPresets`,
|
|
4512
|
+
* 0.1.2-alpha.2) or the connection RPC face
|
|
4513
|
+
* (`connection.api.agentPresets`, hosts below that cohort). Answers
|
|
4514
|
+
* undefined when the host serves neither, so the caller leaves the picker
|
|
4515
|
+
* options untouched instead of erroring.
|
|
4516
|
+
*/
|
|
4517
|
+
async function readPresetRoster(ctx, remote) {
|
|
4518
|
+
let remotes;
|
|
4519
|
+
try {
|
|
4520
|
+
remotes = remote.agentPresets;
|
|
4521
|
+
} catch {
|
|
4522
|
+
remotes = void 0;
|
|
4523
|
+
}
|
|
4524
|
+
if (remotes !== void 0) {
|
|
4525
|
+
const response = await remotes.list();
|
|
4526
|
+
if (!response.ok) return {
|
|
4527
|
+
ok: false,
|
|
4528
|
+
presets: []
|
|
4529
|
+
};
|
|
4530
|
+
return {
|
|
4531
|
+
ok: true,
|
|
4532
|
+
presets: response.value.presets
|
|
4533
|
+
};
|
|
4534
|
+
}
|
|
4535
|
+
const legacy = ctx.get("connection").api?.agentPresets;
|
|
4536
|
+
if (legacy === void 0) return void 0;
|
|
4537
|
+
const response = await legacy.list({});
|
|
4538
|
+
if (!response.result.ok || response.result.value === void 0) return {
|
|
4539
|
+
ok: false,
|
|
4540
|
+
presets: []
|
|
4541
|
+
};
|
|
4542
|
+
return {
|
|
4543
|
+
ok: true,
|
|
4544
|
+
presets: response.result.value.presets ?? []
|
|
4545
|
+
};
|
|
4546
|
+
}
|
|
4547
|
+
/**
|
|
3955
4548
|
* Mount the task board.
|
|
3956
4549
|
* @param ctx - client root context (services: sessions, workspaces).
|
|
3957
4550
|
*/
|
|
@@ -3993,7 +4586,7 @@ window.__ModuleLoader__.load({
|
|
|
3993
4586
|
if (uiDisposer !== void 0) return;
|
|
3994
4587
|
const sessions = ctx.get("sessions");
|
|
3995
4588
|
const workspaces = ctx.get("workspaces");
|
|
3996
|
-
const
|
|
4589
|
+
const remote = ctx.get("remote");
|
|
3997
4590
|
const controller = new BoardController({
|
|
3998
4591
|
store: new LocalStorageTaskStore(),
|
|
3999
4592
|
transport: new HttpTaskBoardHostTransport(),
|
|
@@ -4015,9 +4608,9 @@ window.__ModuleLoader__.load({
|
|
|
4015
4608
|
disposers.push(workspaces.list.subscribe(pushWorkspaceOptions));
|
|
4016
4609
|
const pushPresetOptions = async () => {
|
|
4017
4610
|
try {
|
|
4018
|
-
const
|
|
4019
|
-
if (!
|
|
4020
|
-
controller.setExecutionOptions({ presets:
|
|
4611
|
+
const roster = await readPresetRoster(ctx, remote);
|
|
4612
|
+
if (roster === void 0 || !roster.ok) return;
|
|
4613
|
+
controller.setExecutionOptions({ presets: roster.presets.map((preset) => ({
|
|
4021
4614
|
id: preset.id,
|
|
4022
4615
|
name: preset.name,
|
|
4023
4616
|
description: preset.description,
|