duaer-spec 0.22.0 → 0.23.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.duaer/active-job.json +2 -3
- package/.duaer/specs/205-release-022/delivery.json +10 -0
- package/.duaer/specs/205-release-022/tasks.md +2 -2
- package/.duaer/specs/206-employee-directory/delivery.json +13 -0
- package/.duaer/specs/206-employee-directory/spec.md +32 -0
- package/.duaer/specs/206-employee-directory/tasks.md +6 -0
- package/.duaer/specs/207-fix-projects-root-pick/delivery.json +10 -0
- package/.duaer/specs/207-fix-projects-root-pick/spec.md +20 -0
- package/.duaer/specs/207-fix-projects-root-pick/tasks.md +5 -0
- package/.duaer/specs/208-arch-click-fullscreen/delivery.json +10 -0
- package/.duaer/specs/208-arch-click-fullscreen/spec.md +24 -0
- package/.duaer/specs/208-arch-click-fullscreen/tasks.md +6 -0
- package/.duaer/specs/209-orch-wave-deadlock/delivery.json +10 -0
- package/.duaer/specs/209-orch-wave-deadlock/spec.md +27 -0
- package/.duaer/specs/209-orch-wave-deadlock/tasks.md +5 -0
- package/.duaer/specs/210-release-023/spec.md +12 -0
- package/.duaer/specs/210-release-023/tasks.md +6 -0
- package/CHANGELOG.md +24 -0
- package/bin/duaer-live.mjs +114 -50
- package/bin/live-modules.mjs +31 -3
- package/bin/live-orchestrate.mjs +2 -11
- package/docs/agent/e2e-test-plan.md +9 -7
- package/docs/agent/live-desk.md +7 -1
- package/package.json +1 -1
- package/web/live-dev/app.js +107 -8
- package/web/live-dev/architecture-mount.mjs +16 -55
- package/web/live-dev/employee-catalog.mjs +54 -0
- package/web/live-dev/i18n-ja.js +12 -1
- package/web/live-dev/i18n.js +24 -2
- package/web/live-dev/index.html +26 -0
- package/web/live-dev/styles.css +41 -0
package/bin/live-orchestrate.mjs
CHANGED
|
@@ -137,6 +137,8 @@ export function orchestrationSummary(input = {}) {
|
|
|
137
137
|
|
|
138
138
|
/**
|
|
139
139
|
* Decide which workers need a new wave release.
|
|
140
|
+
* Busy lanes still return reason "ready" — launchAgent FIFO-enqueues
|
|
141
|
+
* continue behind the current session (silent-wait must not block forever).
|
|
140
142
|
* @param {{
|
|
141
143
|
* pool: { tasks?: Array<object> },
|
|
142
144
|
* workerCount: number,
|
|
@@ -153,7 +155,6 @@ export function pendingWaveReleases(input = {}) {
|
|
|
153
155
|
? input.doneSet
|
|
154
156
|
: new Set([...(input.doneSet || [])].map(normId).filter(Boolean));
|
|
155
157
|
const released = input.releasedWaves || {};
|
|
156
|
-
const terminals = input.terminals || {};
|
|
157
158
|
const out = [];
|
|
158
159
|
for (let w = 1; w <= count; w += 1) {
|
|
159
160
|
const workerId = `w${w}`;
|
|
@@ -162,16 +163,6 @@ export function pendingWaveReleases(input = {}) {
|
|
|
162
163
|
const fp = fingerprintWave(wave.map((t) => t.id));
|
|
163
164
|
const prior = Array.isArray(released[workerId]) ? released[workerId] : [];
|
|
164
165
|
if (prior.includes(fp)) continue;
|
|
165
|
-
const term = terminals[workerId] || {};
|
|
166
|
-
if (term.busy || Number(term.queueDepth) > 0) {
|
|
167
|
-
out.push({
|
|
168
|
-
workerId,
|
|
169
|
-
wave,
|
|
170
|
-
fingerprint: fp,
|
|
171
|
-
reason: "lane_busy",
|
|
172
|
-
});
|
|
173
|
-
continue;
|
|
174
|
-
}
|
|
175
166
|
out.push({
|
|
176
167
|
workerId,
|
|
177
168
|
wave,
|
|
@@ -66,7 +66,7 @@ the table for their app.
|
|
|
66
66
|
| E2E-083 | After refresh, previously passed auto-validate keeps Confirm enabled | `npm test` + `npm run test:live` |
|
|
67
67
|
| E2E-084 | Requirements fields edit as structured rows (add/remove), not raw textarea | `npm test` + `npm run test:live` |
|
|
68
68
|
| E2E-085 | Setup 说明 covers Cursor/Claude install + other model wiring; header Model/Settings reopens it; setup.hint names Duaer desk model; guideDoc links locale-specific GitHub worker-models | `npm test` + `npm run test:live` |
|
|
69
|
-
| E2E-086 | After Confirm, architecture dialogue + Archify HTML under hosting (no diagram-container height clip;
|
|
69
|
+
| E2E-086 | After Confirm, architecture dialogue + Archify HTML under hosting (no diagram-container height clip; click diagram opens present fullscreen — embed does not zoom nodes); gate dispatch; revise confirms plan then re-confirms architecture before dispatch; ready chat says diagram is under hosting; Confirm architecture clickable after render | `npm test` + `npm run test:live` |
|
|
70
70
|
| E2E-087 | After「派工并用…启动」succeeds, button stays「已派工」(persisted + restored from status); refresh / client timeout does not re-enable first launch | `npm test` + `npm run test:live` |
|
|
71
71
|
| E2E-088 | After accept, Result panel always visible (查看结果 and/or 打开项目目录); progress column mirrors CTA; no-page jobs still show open-folder | `npm test` + `npm run test:live` |
|
|
72
72
|
| E2E-089 | Revise keeps confirmed architecture; re-confirm only when architecture changes; prior diagram stays above new | `npm test` + `npm run test:live` |
|
|
@@ -89,7 +89,7 @@ the table for their app.
|
|
|
89
89
|
| E2E-107 | 再改一版 reacts on first click (chat focus + kickoff before chrome; no accepted flip) | `npm run test:live` + manual |
|
|
90
90
|
| E2E-108 | Revise kickoff keeps streamed model reply; safe chat done SSE (no stack-overflow wipe) | `npm run test:live` + manual |
|
|
91
91
|
| E2E-109 | 再改一版 never shows Maximum call stack; persist/SSE omit deep IR | `npm run test:live` + manual |
|
|
92
|
-
| E2E-110 | Architecture embed
|
|
92
|
+
| E2E-110 | Architecture embed click opens present fullscreen (no in-embed node zoom / passport) | `npm test` + manual |
|
|
93
93
|
| E2E-111 | Served architecture HTML upgrades stale `#duaer-embed-fit` so passport CSS applies | `npm test` + manual |
|
|
94
94
|
| E2E-112 | Architecture embed passport sits 100px left and 100px up from prior inset | `npm test` + manual |
|
|
95
95
|
| E2E-113 | Architecture embed passport stays in-view (left flush, top 100px, high z-index) | `npm test` + manual |
|
|
@@ -103,8 +103,8 @@ the table for their app.
|
|
|
103
103
|
| E2E-121 | After 改进方案确认, architecture panel sits below「请先确认架构」CTA | `npm run test:live` + manual |
|
|
104
104
|
| E2E-122 | Architecture canvas expands to full diagram height (no iframe); node passport via Shadow mount | `npm test` + `npm run test:live` + manual |
|
|
105
105
|
| E2E-123 | Architecture mounts inline (Shadow DOM), not iframe; GET `.json` for passport | `npm test` + manual |
|
|
106
|
-
| E2E-124 | Inline mount keeps
|
|
107
|
-
| E2E-125 |
|
|
106
|
+
| E2E-124 | Inline mount keeps Archify FX (theme CSS, motion); click opens fullscreen present | `npm test` + manual |
|
|
107
|
+
| E2E-125 | Architecture mount click opens present fullscreen; embed reveal/zoom disabled | `npm test` + manual |
|
|
108
108
|
| E2E-126 | Architecture mount height follows SVG canvas (not 100vh black box) | `npm test` + manual |
|
|
109
109
|
| E2E-127 | Result bar Deploy next to 打开看看; none → GitHub Pages; POST /api/deploy | `npm test` + manual |
|
|
110
110
|
| E2E-128 | Deploy click opens host picker before starting deploy | `npm test` + manual |
|
|
@@ -125,7 +125,7 @@ the table for their app.
|
|
|
125
125
|
| E2E-143 | Cyclic architecture IR does not freeze live desk; deliverables page opens without Google Fonts | `npm test` + manual |
|
|
126
126
|
| E2E-144 | Deliverables HTML has Contents TOC + structured card fields (dl/dt/dd) | `npm test` + manual |
|
|
127
127
|
| E2E-145 | Multi-worker dispatch shows per-employee lanes under 派工进度 | `npm test` + `npm run test:live` + manual |
|
|
128
|
-
| E2E-146 | Wave orchestration: only ready tasks in first prompt; status poll continues next wave when lane
|
|
128
|
+
| E2E-146 | Wave orchestration: only ready tasks in first prompt; status poll continues next wave when prior wave checked (enqueue even if lane busy; agent exits after wave) | `npm test` + manual |
|
|
129
129
|
| E2E-147 | Deliverables HTML: numbered acceptance lists, chips, task table, confirmation registry | `npm test` + manual |
|
|
130
130
|
| E2E-148 | Deliverables page two-column dossier layout (TOC rail + paper body) | `npm test` + manual |
|
|
131
131
|
| E2E-149 | 初版 · 需求卡 accordion body uses structured lists/paragraphs (`req-list` / `req-para`); goal prose does not shatter on `、` | `npm test` + `npm run test:live` |
|
|
@@ -133,7 +133,7 @@ the table for their app.
|
|
|
133
133
|
| E2E-151 | Modular `[模块]` card fields render section titles + numbered acceptance lists | `npm test` + `npm run test:live` |
|
|
134
134
|
| E2E-152 | Project portfolio shows delivery status chips + open deliverables; Progress shows stage next-action | `npm test` + `npm run test:live` |
|
|
135
135
|
| E2E-153 | `duaer live` starts without model config, opens desk URL, Settings guides setup | `npm test` + `npm run test:live` |
|
|
136
|
-
| E2E-154 | 产品父目录 is read-only; 选择… opens native folder pick and saves; 清除 empties; no path typing | `npm test` + manual |
|
|
136
|
+
| E2E-154 | 产品父目录 is read-only; 选择… or path click opens frontmost native folder pick and saves; 清除 empties; no path typing | `npm test` + manual |
|
|
137
137
|
| E2E-155 | FDE dispatch: prompts forbid Job not accepted yet; all-tasks-done nudges stamp accept | `npm test` + `npm run test:live` |
|
|
138
138
|
| E2E-156 | Accepted chat bubble preview URL is a clickable `<a>` (http(s) or /api/…) | `npm test` + `npm run test:live` |
|
|
139
139
|
| E2E-157 | `http://host/api/result/…` chat links render as one clean `<a>` (no nested HTML garble) | `npm test` |
|
|
@@ -143,6 +143,7 @@ the table for their app.
|
|
|
143
143
|
| E2E-162 | Architecture / task-path click opens present=1 fullscreen in a new tab | `npm test` + `npm run test:live` |
|
|
144
144
|
| E2E-163 | Task breakdown: multi-line acceptance → one atomic checkbox task per line; progress monitorable via `- [ ]`/`- [x]` | `npm test` |
|
|
145
145
|
| E2E-164 | Live desk Japanese (`ja`) locale: select option, catalog parity with en, deliverables `lang=ja` | `npm test` + manual |
|
|
146
|
+
| E2E-165 | Top 数字员工 directory lists Implementer + Functional regression; kickoff tasks carry role | `npm test` + `npm run test:live` |
|
|
146
147
|
| E2E-093 | Header top actions share one button chrome; GitHub control links to duaer-spec and shows live stars via `/api/github` | `npm run test:live` + manual |
|
|
147
148
|
| E2E-063 | Duaer-spec FDE brand: header/title show Duaer-spec FDE; zh secondary mark is 现场开发; en mark is Field Development Environment; setup hint / desk-model guide name Duaer | `npm test` + manual |
|
|
148
149
|
| E2E-078 | Claude Terminal launch includes `--permission-mode bypassPermissions` and stamps `hasTrustDialogAccepted` for the worktree | `npm test` + manual |
|
|
@@ -298,7 +299,7 @@ the table for their app.
|
|
|
298
299
|
| E2E-122 | `155-arch-canvas-expand` | canvas expands; no iframe scroll |
|
|
299
300
|
| E2E-123 | `156-arch-inline-mount` | Shadow DOM mount; no iframe |
|
|
300
301
|
| E2E-124 | `157-arch-mount-fx` | full Archify styles + viewer FX |
|
|
301
|
-
| E2E-125 | `158-arch-click-fx` | full body mount; click
|
|
302
|
+
| E2E-125 | `158-arch-click-fx` | full body mount; click → present fullscreen (no embed zoom) |
|
|
302
303
|
| E2E-126 | `159-arch-auto-height` | content-driven height; kill 100vh lock |
|
|
303
304
|
| E2E-127 | `160-preview-deploy-btn` | result-bar Deploy; default Pages |
|
|
304
305
|
| E2E-128 | `161-deploy-picker` | Deploy opens where-to-host dialog |
|
|
@@ -332,3 +333,4 @@ the table for their app.
|
|
|
332
333
|
| E2E-162 | `201-arch-fullscreen` | architecture click → present fullscreen |
|
|
333
334
|
| E2E-163 | `203-atomic-monitorable-tasks` | atomic verifiable tasks; checkbox progress |
|
|
334
335
|
| E2E-164 | `204-live-i18n-ja` | Japanese locale on live desk |
|
|
336
|
+
| E2E-165 | `206-employee-directory` | employee directory + verify-l3 role |
|
package/docs/agent/live-desk.md
CHANGED
|
@@ -46,7 +46,8 @@ HTML page generated from the project session:
|
|
|
46
46
|
|---|---|
|
|
47
47
|
| Requirements | Requirements document (version timeline) · confirmation of modules |
|
|
48
48
|
| Architecture | Diagram link · confirmation note |
|
|
49
|
-
| Kickoff & implementation | Brief/job id · task pool |
|
|
49
|
+
| Kickoff & implementation | Brief/job id · task pool · employee roles |
|
|
50
|
+
|
|
50
51
|
| Delivery | Delivery stamp · preview URL |
|
|
51
52
|
| Revisions | Each revise plan card (when present) |
|
|
52
53
|
|
|
@@ -74,6 +75,11 @@ registry linking into the requirements document (no duplicate full cards).
|
|
|
74
75
|
Kickoff owns Brief creation and dispatch:
|
|
75
76
|
|
|
76
77
|
- Builds a **dependency-aware task pool** (`dependsOn` edges).
|
|
78
|
+
- **Employee directory** (top bar): lists specialized roles — **Implementer**
|
|
79
|
+
and **Functional regression** (Acceptance → testing.md L0–L3 / Playwright).
|
|
80
|
+
Runtime remains Cursor Agent / Claude Code; kickoff tasks carry
|
|
81
|
+
`role: implement | verify-l3`. With workerCount ≥ 2, verify-l3 tasks go to
|
|
82
|
+
the last lane.
|
|
77
83
|
- **Atomic + monitorable:** one checkbox task per independently verifiable
|
|
78
84
|
acceptance line (plus impl / verify / shared steps). Agents must mark
|
|
79
85
|
`- [x]` as each atom completes so the desk can poll progress and release waves.
|
package/package.json
CHANGED
package/web/live-dev/app.js
CHANGED
|
@@ -24,6 +24,7 @@ import {
|
|
|
24
24
|
} from "./architecture-mount.mjs";
|
|
25
25
|
import { enrichChatOptions } from "./choice-options.mjs";
|
|
26
26
|
import { buildTaskArchitectureIr } from "./task-graph.mjs";
|
|
27
|
+
import { EMPLOYEE_CATALOG } from "./employee-catalog.mjs";
|
|
27
28
|
|
|
28
29
|
/** Deliverables API lang: en | ja | zh */
|
|
29
30
|
function deliverablesLang() {
|
|
@@ -351,6 +352,10 @@ const el = {
|
|
|
351
352
|
updateNotice: document.getElementById("updateNotice"),
|
|
352
353
|
langSelect: document.getElementById("langSelect"),
|
|
353
354
|
historyToggle: document.getElementById("historyToggle"),
|
|
355
|
+
employeeToggle: document.getElementById("employeeToggle"),
|
|
356
|
+
employeePanel: document.getElementById("employeePanel"),
|
|
357
|
+
employeeClose: document.getElementById("employeeClose"),
|
|
358
|
+
employeeList: document.getElementById("employeeList"),
|
|
354
359
|
projectBadge: document.getElementById("projectBadge"),
|
|
355
360
|
historyPanel: document.getElementById("historyPanel"),
|
|
356
361
|
historyBackdrop: document.getElementById("historyBackdrop"),
|
|
@@ -1193,13 +1198,21 @@ function bindArchitecturePresentClick(host, getUrl) {
|
|
|
1193
1198
|
if (!host || host.dataset.presentBound === "1") return;
|
|
1194
1199
|
host.dataset.presentBound = "1";
|
|
1195
1200
|
host.classList.add("architecture-mount-clickable");
|
|
1196
|
-
host
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1201
|
+
// Capture on the host so Shadow DOM node clicks open fullscreen instead of
|
|
1202
|
+
// Archify reveal/zoom in the embed.
|
|
1203
|
+
host.addEventListener(
|
|
1204
|
+
"click",
|
|
1205
|
+
(ev) => {
|
|
1206
|
+
if (ev.target.closest?.("button, a, input, textarea, select")) return;
|
|
1207
|
+
const url = typeof getUrl === "function" ? getUrl() : getUrl;
|
|
1208
|
+
const href = String(url || host.dataset.archUrl || "").trim();
|
|
1209
|
+
if (!href) return;
|
|
1210
|
+
ev.preventDefault();
|
|
1211
|
+
ev.stopPropagation();
|
|
1212
|
+
openArchitecturePresent(href);
|
|
1213
|
+
},
|
|
1214
|
+
true,
|
|
1215
|
+
);
|
|
1203
1216
|
}
|
|
1204
1217
|
|
|
1205
1218
|
function hydrateArchitectureMounts(root) {
|
|
@@ -2838,11 +2851,58 @@ function syncDrawerBackdrop() {
|
|
|
2838
2851
|
if (!el.historyBackdrop) return;
|
|
2839
2852
|
const open =
|
|
2840
2853
|
(el.historyPanel && !el.historyPanel.hidden) ||
|
|
2841
|
-
(el.settingsPanel && !el.settingsPanel.hidden)
|
|
2854
|
+
(el.settingsPanel && !el.settingsPanel.hidden) ||
|
|
2855
|
+
(el.employeePanel && !el.employeePanel.hidden);
|
|
2842
2856
|
el.historyBackdrop.hidden = !open;
|
|
2843
2857
|
document.body.classList.toggle("history-open", Boolean(open));
|
|
2844
2858
|
}
|
|
2845
2859
|
|
|
2860
|
+
function setEmployeeOpen(open) {
|
|
2861
|
+
if (!el.employeePanel) return;
|
|
2862
|
+
const want = Boolean(open);
|
|
2863
|
+
if (want) {
|
|
2864
|
+
if (!state.ready) return;
|
|
2865
|
+
if (el.historyPanel && !el.historyPanel.hidden) {
|
|
2866
|
+
el.historyPanel.hidden = true;
|
|
2867
|
+
if (el.historyToggle) el.historyToggle.setAttribute("aria-expanded", "false");
|
|
2868
|
+
}
|
|
2869
|
+
if (el.settingsPanel && !el.settingsPanel.hidden) {
|
|
2870
|
+
el.settingsPanel.hidden = true;
|
|
2871
|
+
if (el.cfgOpen) el.cfgOpen.setAttribute("aria-expanded", "false");
|
|
2872
|
+
}
|
|
2873
|
+
}
|
|
2874
|
+
el.employeePanel.hidden = !want;
|
|
2875
|
+
if (el.employeeToggle) {
|
|
2876
|
+
el.employeeToggle.setAttribute("aria-expanded", want ? "true" : "false");
|
|
2877
|
+
}
|
|
2878
|
+
syncDrawerBackdrop();
|
|
2879
|
+
if (want) renderEmployeeList();
|
|
2880
|
+
}
|
|
2881
|
+
|
|
2882
|
+
function renderEmployeeList() {
|
|
2883
|
+
if (!el.employeeList) return;
|
|
2884
|
+
el.employeeList.replaceChildren();
|
|
2885
|
+
for (const emp of EMPLOYEE_CATALOG) {
|
|
2886
|
+
const row = document.createElement("article");
|
|
2887
|
+
row.className = "employee-card";
|
|
2888
|
+
row.setAttribute("role", "listitem");
|
|
2889
|
+
const name = document.createElement("h3");
|
|
2890
|
+
name.className = "employee-name";
|
|
2891
|
+
name.textContent = t(`employee.${emp.id}.name`);
|
|
2892
|
+
const role = document.createElement("p");
|
|
2893
|
+
role.className = "employee-role meta";
|
|
2894
|
+
role.textContent = t("employee.role", { role: emp.role });
|
|
2895
|
+
const cap = document.createElement("p");
|
|
2896
|
+
cap.className = "employee-cap";
|
|
2897
|
+
cap.textContent = t(`employee.${emp.id}.cap`);
|
|
2898
|
+
const runtime = document.createElement("p");
|
|
2899
|
+
runtime.className = "hint tight";
|
|
2900
|
+
runtime.textContent = t("employee.runtime");
|
|
2901
|
+
row.append(name, role, cap, runtime);
|
|
2902
|
+
el.employeeList.appendChild(row);
|
|
2903
|
+
}
|
|
2904
|
+
}
|
|
2905
|
+
|
|
2846
2906
|
function setSettingsOpen(open) {
|
|
2847
2907
|
if (!el.settingsPanel) return;
|
|
2848
2908
|
const want = Boolean(open);
|
|
@@ -2852,6 +2912,12 @@ function setSettingsOpen(open) {
|
|
|
2852
2912
|
el.historyToggle.setAttribute("aria-expanded", "false");
|
|
2853
2913
|
}
|
|
2854
2914
|
}
|
|
2915
|
+
if (want && el.employeePanel && !el.employeePanel.hidden) {
|
|
2916
|
+
el.employeePanel.hidden = true;
|
|
2917
|
+
if (el.employeeToggle) {
|
|
2918
|
+
el.employeeToggle.setAttribute("aria-expanded", "false");
|
|
2919
|
+
}
|
|
2920
|
+
}
|
|
2855
2921
|
el.settingsPanel.hidden = !want;
|
|
2856
2922
|
if (el.cfgOpen) {
|
|
2857
2923
|
el.cfgOpen.setAttribute("aria-expanded", want ? "true" : "false");
|
|
@@ -5140,6 +5206,7 @@ el.pickProjectsRoot?.addEventListener("click", async () => {
|
|
|
5140
5206
|
el.pickProjectsRoot.disabled = true;
|
|
5141
5207
|
el.pickProjectsRoot.textContent = t("dispatch.pickProjectsRootBusy");
|
|
5142
5208
|
}
|
|
5209
|
+
if (el.projectsRoot) el.projectsRoot.disabled = true;
|
|
5143
5210
|
try {
|
|
5144
5211
|
const res = await fetch("/api/projects/pick-root", { method: "POST" });
|
|
5145
5212
|
const data = await res.json().catch(() => ({}));
|
|
@@ -5168,9 +5235,21 @@ el.pickProjectsRoot?.addEventListener("click", async () => {
|
|
|
5168
5235
|
el.pickProjectsRoot.disabled = false;
|
|
5169
5236
|
el.pickProjectsRoot.textContent = t("dispatch.pickProjectsRoot");
|
|
5170
5237
|
}
|
|
5238
|
+
if (el.projectsRoot) el.projectsRoot.disabled = false;
|
|
5171
5239
|
}
|
|
5172
5240
|
});
|
|
5173
5241
|
|
|
5242
|
+
el.projectsRoot?.addEventListener("click", () => {
|
|
5243
|
+
if (el.pickProjectsRoot?.disabled) return;
|
|
5244
|
+
el.pickProjectsRoot?.click();
|
|
5245
|
+
});
|
|
5246
|
+
el.projectsRoot?.addEventListener("keydown", (ev) => {
|
|
5247
|
+
if (ev.key !== "Enter" && ev.key !== " ") return;
|
|
5248
|
+
ev.preventDefault();
|
|
5249
|
+
if (el.pickProjectsRoot?.disabled) return;
|
|
5250
|
+
el.pickProjectsRoot?.click();
|
|
5251
|
+
});
|
|
5252
|
+
|
|
5174
5253
|
el.clearProjectsRoot?.addEventListener("click", async () => {
|
|
5175
5254
|
if (el.historyErr) el.historyErr.hidden = true;
|
|
5176
5255
|
try {
|
|
@@ -7103,6 +7182,12 @@ function setHistoryOpen(open) {
|
|
|
7103
7182
|
el.settingsPanel.hidden = true;
|
|
7104
7183
|
if (el.cfgOpen) el.cfgOpen.setAttribute("aria-expanded", "false");
|
|
7105
7184
|
}
|
|
7185
|
+
if (want && el.employeePanel && !el.employeePanel.hidden) {
|
|
7186
|
+
el.employeePanel.hidden = true;
|
|
7187
|
+
if (el.employeeToggle) {
|
|
7188
|
+
el.employeeToggle.setAttribute("aria-expanded", "false");
|
|
7189
|
+
}
|
|
7190
|
+
}
|
|
7106
7191
|
el.historyPanel.hidden = !want;
|
|
7107
7192
|
if (el.historyToggle) {
|
|
7108
7193
|
el.historyToggle.setAttribute("aria-expanded", want ? "true" : "false");
|
|
@@ -7480,6 +7565,15 @@ if (el.historyToggle) {
|
|
|
7480
7565
|
setHistoryOpen(open);
|
|
7481
7566
|
});
|
|
7482
7567
|
}
|
|
7568
|
+
if (el.employeeToggle) {
|
|
7569
|
+
el.employeeToggle.addEventListener("click", () => {
|
|
7570
|
+
const open = el.employeePanel?.hidden !== false;
|
|
7571
|
+
setEmployeeOpen(open);
|
|
7572
|
+
});
|
|
7573
|
+
}
|
|
7574
|
+
if (el.employeeClose) {
|
|
7575
|
+
el.employeeClose.addEventListener("click", () => setEmployeeOpen(false));
|
|
7576
|
+
}
|
|
7483
7577
|
el.openDeliverables?.addEventListener("click", () => {
|
|
7484
7578
|
openDeliverablesPage();
|
|
7485
7579
|
});
|
|
@@ -7492,6 +7586,10 @@ if (el.historyBackdrop) {
|
|
|
7492
7586
|
if (state.ready) setSettingsOpen(false);
|
|
7493
7587
|
return;
|
|
7494
7588
|
}
|
|
7589
|
+
if (el.employeePanel && !el.employeePanel.hidden) {
|
|
7590
|
+
setEmployeeOpen(false);
|
|
7591
|
+
return;
|
|
7592
|
+
}
|
|
7495
7593
|
setHistoryOpen(false);
|
|
7496
7594
|
});
|
|
7497
7595
|
}
|
|
@@ -7554,6 +7652,7 @@ onLocaleChange(() => {
|
|
|
7554
7652
|
renderProjectList();
|
|
7555
7653
|
renderWorkerCountList();
|
|
7556
7654
|
syncTaskPoolPreview();
|
|
7655
|
+
if (el.employeePanel && !el.employeePanel.hidden) renderEmployeeList();
|
|
7557
7656
|
if (el.previewPanel && !el.previewPanel.hidden && state.lastStatus) {
|
|
7558
7657
|
renderPreview(state.lastStatus);
|
|
7559
7658
|
} else {
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Mount Archify architecture HTML into a page host (no iframe).
|
|
3
|
-
* Full styles + viewer runtime (
|
|
4
|
-
*
|
|
3
|
+
* Full styles + viewer runtime (motion overlays) via Shadow DOM and a
|
|
4
|
+
* document proxy scoped to `.archify-root`. Desk clicks open fullscreen
|
|
5
|
+
* present view; embed node zoom is disabled.
|
|
5
6
|
*
|
|
6
7
|
* Must include the full <body> chrome (toolbar buttons etc.) — Archify's
|
|
7
8
|
* viewer script null-derefs if #btn-preset / #btn-theme are missing.
|
|
@@ -61,8 +62,9 @@ function hostChromeCss() {
|
|
|
61
62
|
box-sizing: border-box;
|
|
62
63
|
font-family: "JetBrains Mono", ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
|
|
63
64
|
}
|
|
64
|
-
/* Desk: hide Archify chrome; keep diagram FX
|
|
65
|
-
DOM nodes stay present so the viewer script can bind them.
|
|
65
|
+
/* Desk: hide Archify chrome; keep diagram FX.
|
|
66
|
+
DOM nodes stay present so the viewer script can bind them.
|
|
67
|
+
focus-chip stays hidden — click opens fullscreen, not node passport. */
|
|
66
68
|
.archify-root .toolbar,
|
|
67
69
|
.archify-root .header,
|
|
68
70
|
.archify-root .cards,
|
|
@@ -114,25 +116,8 @@ function hostChromeCss() {
|
|
|
114
116
|
max-height: none !important;
|
|
115
117
|
}
|
|
116
118
|
.archify-root .focus-chip {
|
|
117
|
-
display: block !important;
|
|
118
|
-
position: absolute !important;
|
|
119
|
-
left: 0.75rem !important;
|
|
120
|
-
top: 0.75rem !important;
|
|
121
|
-
z-index: 10000 !important;
|
|
122
|
-
width: min(22rem, calc(100% - 1.5rem - 100px)) !important;
|
|
123
|
-
max-width: calc(100% - 1.5rem - 100px) !important;
|
|
124
|
-
max-height: none !important;
|
|
125
|
-
overflow: visible !important;
|
|
126
|
-
pointer-events: auto !important;
|
|
127
|
-
}
|
|
128
|
-
.archify-root .focus-chip[hidden] {
|
|
129
119
|
display: none !important;
|
|
130
120
|
}
|
|
131
|
-
.archify-root .focus-chip .relationship-lens-list {
|
|
132
|
-
display: block !important;
|
|
133
|
-
max-height: none !important;
|
|
134
|
-
overflow: visible !important;
|
|
135
|
-
}
|
|
136
121
|
`;
|
|
137
122
|
}
|
|
138
123
|
|
|
@@ -280,41 +265,17 @@ function createScopedDocument(rootEl, shadow) {
|
|
|
280
265
|
});
|
|
281
266
|
}
|
|
282
267
|
|
|
283
|
-
|
|
268
|
+
/**
|
|
269
|
+
* Desk mounts open fullscreen on click; do not zoom/enlarge nodes in-embed.
|
|
270
|
+
* Keep Archify.view.reveal as a no-op so residual handlers cannot frame zoom.
|
|
271
|
+
*/
|
|
272
|
+
function disableEmbedNodeZoom(Archify) {
|
|
284
273
|
if (!Archify?.view || typeof Archify.view.reveal !== "function") return;
|
|
285
|
-
if (Archify.view.
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
const opts = Object.assign({}, options || {}, {
|
|
289
|
-
includeNeighbors: false,
|
|
290
|
-
maxScale: 2.6,
|
|
291
|
-
padding: 28,
|
|
292
|
-
});
|
|
293
|
-
let forced = false;
|
|
294
|
-
const desc = Object.getOwnPropertyDescriptor(window, "innerWidth");
|
|
295
|
-
try {
|
|
296
|
-
if ((window.innerWidth || 0) <= 720) {
|
|
297
|
-
Object.defineProperty(window, "innerWidth", {
|
|
298
|
-
configurable: true,
|
|
299
|
-
get() {
|
|
300
|
-
return 1280;
|
|
301
|
-
},
|
|
302
|
-
});
|
|
303
|
-
forced = true;
|
|
304
|
-
}
|
|
305
|
-
return original(ids, opts);
|
|
306
|
-
} finally {
|
|
307
|
-
if (forced) {
|
|
308
|
-
try {
|
|
309
|
-
if (desc) Object.defineProperty(window, "innerWidth", desc);
|
|
310
|
-
else delete window.innerWidth;
|
|
311
|
-
} catch {
|
|
312
|
-
/* ignore */
|
|
313
|
-
}
|
|
314
|
-
}
|
|
315
|
-
}
|
|
274
|
+
if (Archify.view.__duaerEmbedNoZoom) return;
|
|
275
|
+
Archify.view.reveal = function duaerNoZoomReveal() {
|
|
276
|
+
/* desk: click opens present fullscreen instead */
|
|
316
277
|
};
|
|
317
|
-
Archify.view.
|
|
278
|
+
Archify.view.__duaerEmbedNoZoom = true;
|
|
318
279
|
}
|
|
319
280
|
|
|
320
281
|
function runViewerScript(code, scopedDocument) {
|
|
@@ -325,7 +286,7 @@ function runViewerScript(code, scopedDocument) {
|
|
|
325
286
|
`"use strict";\n${code}\n;return typeof Archify !== "undefined" ? Archify : null;`,
|
|
326
287
|
);
|
|
327
288
|
const Archify = runner(scopedDocument, window);
|
|
328
|
-
|
|
289
|
+
disableEmbedNodeZoom(Archify);
|
|
329
290
|
return Archify;
|
|
330
291
|
}
|
|
331
292
|
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Digital-employee directory: specialized roles for FDE kickoff / UI.
|
|
3
|
+
* Runtime is still Cursor Agent or Claude Code; role constrains the Brief prompt.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export const EMPLOYEE_ROLES = {
|
|
7
|
+
IMPLEMENT: "implement",
|
|
8
|
+
VERIFY_L3: "verify-l3",
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
/** Built-in catalog (id stable for i18n keys employee.<id>.*). */
|
|
12
|
+
export const EMPLOYEE_CATALOG = [
|
|
13
|
+
{
|
|
14
|
+
id: "implementer",
|
|
15
|
+
role: EMPLOYEE_ROLES.IMPLEMENT,
|
|
16
|
+
runtime: "cursor-or-claude",
|
|
17
|
+
defaultOn: true,
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
id: "regression",
|
|
21
|
+
role: EMPLOYEE_ROLES.VERIFY_L3,
|
|
22
|
+
runtime: "cursor-or-claude",
|
|
23
|
+
defaultOn: true,
|
|
24
|
+
},
|
|
25
|
+
];
|
|
26
|
+
|
|
27
|
+
export function employeeByRole(role) {
|
|
28
|
+
return EMPLOYEE_CATALOG.find((e) => e.role === role) || null;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function clipEmployeeRole(raw) {
|
|
32
|
+
const r = String(raw || "").trim();
|
|
33
|
+
if (r === EMPLOYEE_ROLES.VERIFY_L3) return EMPLOYEE_ROLES.VERIFY_L3;
|
|
34
|
+
return EMPLOYEE_ROLES.IMPLEMENT;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Chinese dispatch blurb for a worker's owned roles (inserted into kickoff prompt).
|
|
39
|
+
*/
|
|
40
|
+
export function rolePromptZh(roles) {
|
|
41
|
+
const set = new Set((roles || []).map(clipEmployeeRole));
|
|
42
|
+
const parts = [];
|
|
43
|
+
if (set.has(EMPLOYEE_ROLES.IMPLEMENT)) {
|
|
44
|
+
parts.push(
|
|
45
|
+
"【角色·实现】只做 role=implement 任务:按 Acceptance 实现功能;不要抢做 verify-l3 回归套件(除非本波只有你一人)。",
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
if (set.has(EMPLOYEE_ROLES.VERIFY_L3)) {
|
|
49
|
+
parts.push(
|
|
50
|
+
"【角色·功能回归】只做 role=verify-l3 任务:对照 Acceptance,按产品 .duaer/memory/testing.md 跑确定性回归(优先 Playwright / npm test / npm run test:live 等 L0–L3);写/补测试、执行并记录证据;不要改业务功能范围。失败则留下可复现说明,不要 stamp accepted。",
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
return parts.join("\n");
|
|
54
|
+
}
|
package/web/live-dev/i18n-ja.js
CHANGED
|
@@ -20,6 +20,17 @@ export const ja = {
|
|
|
20
20
|
"history.meta": "{status} · {time}",
|
|
21
21
|
"history.detailMeta": "{id} · {status} · {time}",
|
|
22
22
|
"project.toggle": "プロジェクト",
|
|
23
|
+
"employee.toggle": "デジタル従業員",
|
|
24
|
+
"employee.mark": "Employees",
|
|
25
|
+
"employee.title": "デジタル従業員",
|
|
26
|
+
"employee.close": "閉じる",
|
|
27
|
+
"employee.lead": "キックオフはタスク役割で割当。実行は Cursor Agent / Claude Code。",
|
|
28
|
+
"employee.role": "役割 {role}",
|
|
29
|
+
"employee.runtime": "ランタイム · Cursor / Claude",
|
|
30
|
+
"employee.implementer.name": "実装従業員",
|
|
31
|
+
"employee.implementer.cap": "Acceptance に沿って実装。README 更新、プレビュー起動、delivery stamp。",
|
|
32
|
+
"employee.regression.name": "機能回帰",
|
|
33
|
+
"employee.regression.cap": "Acceptance → testing.md L0–L3 / Playwright。テスト作成・実行・証拠。業務範囲は変更しない。",
|
|
23
34
|
"project.mark": "Project",
|
|
24
35
|
"project.title": "プロジェクトとチャット",
|
|
25
36
|
"project.close": "閉じる",
|
|
@@ -187,7 +198,7 @@ export const ja = {
|
|
|
187
198
|
"dispatch.taskGraph": "タスク実行パス",
|
|
188
199
|
"dispatch.taskGraphHint": "システムアーキテクチャと同じレイアウト;色はワーカーレーン。図をクリックで全画面",
|
|
189
200
|
"dispatch.workerCount": "デジタル従業員",
|
|
190
|
-
"dispatch.workerCountHint": "既定は 1
|
|
201
|
+
"dispatch.workerCountHint": "既定は 1 の直列;複数時は最終レーンが機能回帰(verify-l3)",
|
|
191
202
|
"dispatch.workerSerial": "直列",
|
|
192
203
|
"dispatch.workerParallel": "並列",
|
|
193
204
|
"dispatch.needModules": "先に全モジュールを確認してください",
|
package/web/live-dev/i18n.js
CHANGED
|
@@ -29,6 +29,17 @@ const zhCN = {
|
|
|
29
29
|
"history.meta": "{status} · {time}",
|
|
30
30
|
"history.detailMeta": "{id} · {status} · {time}",
|
|
31
31
|
"project.toggle": "项目",
|
|
32
|
+
"employee.toggle": "数字员工",
|
|
33
|
+
"employee.mark": "Employees",
|
|
34
|
+
"employee.title": "数字员工",
|
|
35
|
+
"employee.close": "关闭",
|
|
36
|
+
"employee.lead": "派工时按任务角色分配;运行时仍是 Cursor Agent / Claude Code。",
|
|
37
|
+
"employee.role": "角色 {role}",
|
|
38
|
+
"employee.runtime": "运行时 · Cursor / Claude",
|
|
39
|
+
"employee.implementer.name": "实现员工",
|
|
40
|
+
"employee.implementer.cap": "按 Acceptance 实现功能;更新 README;启动预览并 stamp 交付。",
|
|
41
|
+
"employee.regression.name": "功能回归",
|
|
42
|
+
"employee.regression.cap": "Acceptance → testing.md L0–L3 / Playwright;写测、跑测、留证据;不改业务范围。",
|
|
32
43
|
"project.mark": "Project",
|
|
33
44
|
"project.title": "项目与对话",
|
|
34
45
|
"project.close": "关闭",
|
|
@@ -198,7 +209,7 @@ const zhCN = {
|
|
|
198
209
|
"dispatch.taskGraph": "任务执行路径",
|
|
199
210
|
"dispatch.taskGraphHint": "与系统架构图同一排版;色条表示数字员工分工。点击图可新页面全屏查看",
|
|
200
211
|
"dispatch.workerCount": "数字员工数量",
|
|
201
|
-
"dispatch.workerCountHint": "默认 1
|
|
212
|
+
"dispatch.workerCountHint": "默认 1 人串行;多人时末位泳道做功能回归(verify-l3)",
|
|
202
213
|
"dispatch.workerSerial": "串行",
|
|
203
214
|
"dispatch.workerParallel": "并行",
|
|
204
215
|
"dispatch.needModules": "请先确认全部模块",
|
|
@@ -548,6 +559,17 @@ const en = {
|
|
|
548
559
|
"history.meta": "{status} · {time}",
|
|
549
560
|
"history.detailMeta": "{id} · {status} · {time}",
|
|
550
561
|
"project.toggle": "Projects",
|
|
562
|
+
"employee.toggle": "Employees",
|
|
563
|
+
"employee.mark": "Employees",
|
|
564
|
+
"employee.title": "Digital employees",
|
|
565
|
+
"employee.close": "Close",
|
|
566
|
+
"employee.lead": "Kickoff assigns by task role; runtime is still Cursor Agent / Claude Code.",
|
|
567
|
+
"employee.role": "Role {role}",
|
|
568
|
+
"employee.runtime": "Runtime · Cursor / Claude",
|
|
569
|
+
"employee.implementer.name": "Implementer",
|
|
570
|
+
"employee.implementer.cap": "Build to Acceptance; update README; start preview and stamp delivery.",
|
|
571
|
+
"employee.regression.name": "Functional regression",
|
|
572
|
+
"employee.regression.cap": "Acceptance → testing.md L0–L3 / Playwright; write, run, evidence; no product-scope edits.",
|
|
551
573
|
"project.mark": "Project",
|
|
552
574
|
"project.title": "Projects & chats",
|
|
553
575
|
"project.close": "Close",
|
|
@@ -722,7 +744,7 @@ const en = {
|
|
|
722
744
|
"dispatch.taskGraphHint": "Same layout as system architecture; colors show worker lanes. Click the diagram for fullscreen",
|
|
723
745
|
"dispatch.workerCount": "Digital employees",
|
|
724
746
|
"dispatch.workerCountHint":
|
|
725
|
-
"Default 1 serial;
|
|
747
|
+
"Default 1 serial; with more workers the last lane owns verify-l3 regression",
|
|
726
748
|
"dispatch.workerSerial": "Serial",
|
|
727
749
|
"dispatch.workerParallel": "Parallel",
|
|
728
750
|
"dispatch.needModules": "Confirm all modules first",
|
package/web/live-dev/index.html
CHANGED
|
@@ -32,6 +32,15 @@
|
|
|
32
32
|
<button type="button" class="top-btn" id="historyToggle" data-i18n="project.toggle">
|
|
33
33
|
项目
|
|
34
34
|
</button>
|
|
35
|
+
<button
|
|
36
|
+
type="button"
|
|
37
|
+
class="top-btn"
|
|
38
|
+
id="employeeToggle"
|
|
39
|
+
data-i18n="employee.toggle"
|
|
40
|
+
aria-expanded="false"
|
|
41
|
+
>
|
|
42
|
+
数字员工
|
|
43
|
+
</button>
|
|
35
44
|
<button type="button" class="top-btn" id="cfgOpen" data-i18n="setup.open" aria-expanded="false">
|
|
36
45
|
设置
|
|
37
46
|
</button>
|
|
@@ -346,8 +355,11 @@
|
|
|
346
355
|
id="projectsRoot"
|
|
347
356
|
type="text"
|
|
348
357
|
readonly
|
|
358
|
+
class="projects-root-field"
|
|
349
359
|
data-i18n-placeholder="dispatch.projectsRootEmpty"
|
|
360
|
+
data-i18n-title="dispatch.pickProjectsRoot"
|
|
350
361
|
placeholder="尚未选择"
|
|
362
|
+
title="选择…"
|
|
351
363
|
autocomplete="off"
|
|
352
364
|
/>
|
|
353
365
|
</label>
|
|
@@ -389,6 +401,20 @@
|
|
|
389
401
|
</div>
|
|
390
402
|
</aside>
|
|
391
403
|
|
|
404
|
+
<aside id="employeePanel" class="history-drawer panel employee-drawer" hidden>
|
|
405
|
+
<div class="history-head">
|
|
406
|
+
<div>
|
|
407
|
+
<p class="mark" data-i18n="employee.mark">Employees</p>
|
|
408
|
+
<h2 class="history-title" data-i18n="employee.title">数字员工</h2>
|
|
409
|
+
</div>
|
|
410
|
+
<button type="button" class="btn history-close" id="employeeClose" data-i18n="employee.close">
|
|
411
|
+
关闭
|
|
412
|
+
</button>
|
|
413
|
+
</div>
|
|
414
|
+
<p class="hint tight" data-i18n="employee.lead">派工时按任务角色分配;运行时仍是 Cursor Agent / Claude Code。</p>
|
|
415
|
+
<div id="employeeList" class="employee-list" role="list"></div>
|
|
416
|
+
</aside>
|
|
417
|
+
|
|
392
418
|
<aside id="settingsPanel" class="history-drawer panel settings-drawer" hidden>
|
|
393
419
|
<div class="history-head">
|
|
394
420
|
<div>
|