@hanzlaa/rcode 4.1.1 → 4.3.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/AGENTS.md +1 -1
- package/CONTRIBUTING.md +3 -0
- package/README.md +3 -0
- package/cli/agent.js +3 -1
- package/cli/index.js +29 -0
- package/cli/install.js +233 -15
- package/cli/lib/config.cjs +4 -2
- package/cli/lib/fsutil.cjs +13 -2
- package/cli/lib/homedir.cjs +21 -0
- package/cli/lib/schemas.cjs +6 -1
- package/cli/nuke.js +13 -8
- package/cli/postinstall.js +14 -4
- package/cli/rcode-slash-router.cjs +118 -0
- package/cli/uninstall.js +59 -1
- package/cli/update.js +10 -5
- package/cli/workflow.js +3 -1
- package/dist/rcode.js +241 -227
- package/package.json +1 -1
- package/rcode/bin/rcode-tools.cjs +15 -6
- package/rcode/commands/scaffold-project.md +2 -2
- package/rcode/skills/actions/2-plan/rcode-create-epics-and-stories/steps/step-04-final-validation.md +1 -1
- package/rcode/skills/actions/2-plan/rcode-create-milestone/steps/README.md +2 -2
- package/rcode/skills/actions/2-plan/rcode-create-milestone/steps/step-09-state-sync.md +1 -1
- package/rcode/skills/actions/4-implementation/rcode-code-review/steps/step-02-review.md +1 -1
- package/rcode/skills/actions/4-implementation/rcode-git-flow/SKILL.md +1 -1
- package/rcode/skills/actions/4-implementation/rcode-scaffold-project/SKILL.md +39 -12
- package/rcode/skills/actions/4-implementation/rcode-scaffold-project/steps/step-01-target.md +18 -3
- package/rcode/skills/actions/4-implementation/rcode-scaffold-project/steps/step-02-safety.md +27 -3
- package/rcode/skills/actions/4-implementation/rcode-scaffold-project/steps/step-03-brownfield.md +57 -0
- package/rcode/skills/actions/4-implementation/rcode-scaffold-project/steps/step-03-clone.md +4 -1
- package/rcode/skills/actions/4-implementation/rcode-scaffold-project/steps/step-04-post-setup.md +15 -1
- package/rcode/skills/actions/4-implementation/rcode-trim/SKILL.md +1 -1
- package/rcode/workflows/audit-milestone.md +1 -1
- package/rcode/workflows/discuss-phase.md +1 -1
- package/rcode/workflows/execute-milestone.md +1 -1
- package/rcode/workflows/execute-regression-gates.md +3 -0
- package/rcode/workflows/execute-sprint.md +27 -1
- package/rcode/workflows/execute-waves.md +6 -0
- package/rcode/workflows/execute.md +13 -3
- package/rcode/workflows/new-milestone.md +2 -2
- package/rcode/workflows/new-project.md +4 -0
- package/rcode/workflows/plan-research-validation.md +1 -1
- package/rcode/workflows/plan-spawn-planner.md +2 -2
- package/rcode/workflows/plan.md +34 -15
- package/rcode/workflows/review.md +2 -0
- package/rcode/workflows/scaffold-project.md +5 -1
- package/rcode/workflows/session-report.md +1 -1
- package/rcode/workflows/ship.md +39 -0
- package/rcode/workflows/sprint-planning.md +27 -0
- package/rcode/workflows/status.md +3 -3
- package/server/dashboard.js +26 -7
- package/server/lib/api.js +62 -4
- package/server/lib/html/client/agents-data.js +22 -18
- package/server/lib/html/client/app.js +3 -0
- package/server/lib/html/client/components/AgentCard.js +127 -0
- package/server/lib/html/client/components/App.js +104 -39
- package/server/lib/html/client/components/CommandPalette.js +133 -0
- package/server/lib/html/client/components/FileReader.js +116 -0
- package/server/lib/html/client/components/FilterChips.js +94 -0
- package/server/lib/html/client/components/NotifyCenter.js +117 -0
- package/server/lib/html/client/components/OrchPanel.js +80 -52
- package/server/lib/html/client/components/PhaseGraph.js +300 -0
- package/server/lib/html/client/components/RejectDialog.js +78 -0
- package/server/lib/html/client/components/RunnerPicker.js +190 -0
- package/server/lib/html/client/components/Sidebar.js +106 -61
- package/server/lib/html/client/components/StatusSummaryBar.js +76 -0
- package/server/lib/html/client/components/TaskPipeline.js +83 -0
- package/server/lib/html/client/components/Topbar.js +86 -39
- package/server/lib/html/client/components/dashboard/Blockers.js +57 -0
- package/server/lib/html/client/components/dashboard/CompletedTasks.js +47 -0
- package/server/lib/html/client/components/dashboard/CurrentPhase.js +107 -0
- package/server/lib/html/client/components/dashboard/InProgress.js +72 -0
- package/server/lib/html/client/components/dashboard/ProgressDonut.js +101 -0
- package/server/lib/html/client/components/dashboard/ProgressTimeline.js +101 -0
- package/server/lib/html/client/components/dashboard/ProjectHealth.js +80 -0
- package/server/lib/html/client/components/dashboard/RecentDecisions.js +57 -0
- package/server/lib/html/client/components/dashboard/Timeline.js +143 -0
- package/server/lib/html/client/components/shared.js +47 -11
- package/server/lib/html/client/filter-state.js +72 -0
- package/server/lib/html/client/icons-client.js +7 -0
- package/server/lib/html/client/notify.js +75 -0
- package/server/lib/html/client/orchestrator.js +168 -41
- package/server/lib/html/client/preact.js +13 -8
- package/server/lib/html/client/store.js +70 -6
- package/server/lib/html/client/util.js +78 -0
- package/server/lib/html/client/vendor/htm.js +1 -0
- package/server/lib/html/client/vendor/preact-hooks.js +2 -0
- package/server/lib/html/client/vendor/preact.js +2 -0
- package/server/lib/html/client/views/AgentsView.js +144 -51
- package/server/lib/html/client/views/FilesView.js +20 -103
- package/server/lib/html/client/views/KanbanView.js +40 -21
- package/server/lib/html/client/views/MemoryView.js +26 -9
- package/server/lib/html/client/views/MilestonesView.js +4 -4
- package/server/lib/html/client/views/OrchestrationView.js +154 -19
- package/server/lib/html/client/views/OverviewView.js +47 -239
- package/server/lib/html/client/views/PhasesView.js +50 -6
- package/server/lib/html/client/views/RoadmapView.js +6 -3
- package/server/lib/html/client/views/SprintsView.js +50 -6
- package/server/lib/html/client/views/TasksView.js +4 -3
- package/server/lib/html/client.js +21 -4
- package/server/lib/html/css.js +2761 -8
- package/server/lib/html/icons.js +7 -0
- package/server/lib/html/shell.js +10 -3
- package/server/lib/scanner.js +376 -39
- package/server/orchestrator.js +329 -5
package/rcode/workflows/plan.md
CHANGED
|
@@ -64,6 +64,20 @@ Valid rcode subagent types (use exact names — do not fall back to 'general-pur
|
|
|
64
64
|
|
|
65
65
|
<process>
|
|
66
66
|
|
|
67
|
+
## 0. Project-Status Preflight
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
PROJECT_STATUS=$(node .rcode/bin/rcode-tools.cjs project-status 2>/dev/null || echo uninitialized)
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
If `PROJECT_STATUS` is `uninstalled`, `uninitialized`, or `stub`:
|
|
74
|
+
|
|
75
|
+
```
|
|
76
|
+
Project not initialized. Run /rcode-init first (or /rcode-new-project for a greenfield project), then return here.
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
Stop. Do not proceed until `project-status` returns `real`.
|
|
80
|
+
|
|
67
81
|
## 1. Initialize
|
|
68
82
|
|
|
69
83
|
Load all context in one call (paths only to minimize orchestrator context):
|
|
@@ -117,7 +131,7 @@ fi
|
|
|
117
131
|
|
|
118
132
|
When `GAPS_MODE=true`, the workflow switches to **gap-closure planning**: read the phase's VERIFICATION.md, extract verification gaps classified `gap_found` or `partial`, and produce a single new numbered plan file (`NNN-NN-SPRINT.md`) that closes them. Research, CONTEXT.md gating, and VALIDATION.md creation are skipped — gaps are grounded in already-shipped code, not new design work.
|
|
119
133
|
|
|
120
|
-
**Detect from-stub mode
|
|
134
|
+
**Detect from-stub mode:**
|
|
121
135
|
```bash
|
|
122
136
|
if [[ "$ARGUMENTS" =~ (^|[[:space:]])--from-stub($|[[:space:]]) ]]; then
|
|
123
137
|
FROM_STUB_MODE=true
|
|
@@ -154,7 +168,7 @@ mkdir -p ".planning/phases/${padded_phase}-${phase_slug}"
|
|
|
154
168
|
|
|
155
169
|
**Existing artifacts from init:** `has_research`, `has_plans`, `plan_count`.
|
|
156
170
|
|
|
157
|
-
**TASKS.md ingestion
|
|
171
|
+
**TASKS.md ingestion.** If the phase directory contains a `TASKS.md` file (typically auto-extracted by `/rcode-add-phase` from a bulk `/rcode-quick` or `/rcode-do` route), read it now:
|
|
158
172
|
|
|
159
173
|
```bash
|
|
160
174
|
TASKS_FILE=".planning/phases/${padded_phase}-${phase_slug}/TASKS.md"
|
|
@@ -187,6 +201,12 @@ Exit workflow.
|
|
|
187
201
|
## 3. Validate Phase
|
|
188
202
|
|
|
189
203
|
```bash
|
|
204
|
+
# Stub-ROADMAP guard — emit a warning if ROADMAP.md has no real phase headings.
|
|
205
|
+
ROADMAP_PHASE_COUNT=$(grep -c "^## Phase " "${ROADMAP_PATH}" 2>/dev/null || echo 0)
|
|
206
|
+
if [ "${ROADMAP_PHASE_COUNT}" -eq 0 ]; then
|
|
207
|
+
echo "⚠ WARN: ROADMAP.md appears to be a stub — add real ## Phase headings before running plan."
|
|
208
|
+
exit 1
|
|
209
|
+
fi
|
|
190
210
|
PHASE_INFO=$(node ".rcode/bin/rcode-tools.cjs" roadmap get-phase "${PHASE}")
|
|
191
211
|
```
|
|
192
212
|
|
|
@@ -326,7 +346,7 @@ Otherwise use AskUserQuestion:
|
|
|
326
346
|
If "Continue without context": Proceed to step 5.
|
|
327
347
|
If "Run discuss-phase first":
|
|
328
348
|
**IMPORTANT:** Do NOT invoke discuss-phase as a nested Skill/Task call — AskUserQuestion
|
|
329
|
-
does not work correctly in nested subcontexts
|
|
349
|
+
does not work correctly in nested subcontexts. Instead, display the command
|
|
330
350
|
and exit so the user runs it as a top-level command:
|
|
331
351
|
```
|
|
332
352
|
Run this command first, then re-run /rcode-plan {X} ${RCODE_WS}:
|
|
@@ -362,7 +382,7 @@ Always offer exactly three numbered options:
|
|
|
362
382
|
|
|
363
383
|
Wait for the user's choice before proceeding. Do not auto-select.
|
|
364
384
|
|
|
365
|
-
**If user picks option 1 (Add more plans)
|
|
385
|
+
**If user picks option 1 (Add more plans):**
|
|
366
386
|
|
|
367
387
|
This is **NOT** a license to hand-write a new SPRINT.md inline. Continue down the
|
|
368
388
|
normal pipeline exactly as if no plans existed yet:
|
|
@@ -376,8 +396,7 @@ normal pipeline exactly as if no plans existed yet:
|
|
|
376
396
|
first-time plan. The "PLANNED ✓" banner is gated on a passing CHECK.md.
|
|
377
397
|
|
|
378
398
|
A run that emits a SPRINT.md without a corresponding planner Task() invocation
|
|
379
|
-
in the same turn is a malfunction
|
|
380
|
-
shipping a hand-rolled plan.
|
|
399
|
+
in the same turn is a malfunction. Stop and report instead of shipping a hand-rolled plan.
|
|
381
400
|
|
|
382
401
|
**If user picks option 3 (Replan from scratch):**
|
|
383
402
|
|
|
@@ -390,7 +409,7 @@ still mandatory.
|
|
|
390
409
|
|
|
391
410
|
Display a sprint summary table (sprint id → one-line goal).
|
|
392
411
|
|
|
393
|
-
Then run a **best-effort codebase overlap check** before showing the execute prompt
|
|
412
|
+
Then run a **best-effort codebase overlap check** before showing the execute prompt.
|
|
394
413
|
|
|
395
414
|
**This check is always informational. It never blocks, never errors, never fails the workflow.** If any step below cannot complete for any reason, skip it silently and proceed straight to the execute prompt.
|
|
396
415
|
|
|
@@ -534,7 +553,7 @@ It never silently passes a plan where two sprints create the same file.
|
|
|
534
553
|
- **`## CHECKPOINT REACHED`:** Present to user, get response, spawn continuation (step 12)
|
|
535
554
|
- **`## PLANNING INCONCLUSIVE`:** Show attempts, offer: Add context / Retry / Manual
|
|
536
555
|
|
|
537
|
-
**Sprint count guard (token cost protection
|
|
556
|
+
**Sprint count guard (token cost protection):**
|
|
538
557
|
|
|
539
558
|
After planner returns `## PLANNING COMPLETE`, immediately count sprint files:
|
|
540
559
|
|
|
@@ -670,7 +689,7 @@ If thinking_partner disabled: skip this block entirely.
|
|
|
670
689
|
|
|
671
690
|
## 12. Revision Loop (Max 3 Iterations, 1 in autonomous/yolo mode)
|
|
672
691
|
|
|
673
|
-
**Mode-based iteration cap (token cost protection
|
|
692
|
+
**Mode-based iteration cap (token cost protection):**
|
|
674
693
|
|
|
675
694
|
```bash
|
|
676
695
|
MAX_ITERATIONS=$($TOOL config-get workflow.max_checker_iterations 2>/dev/null || echo "")
|
|
@@ -686,7 +705,7 @@ Track `stall_reentry_count` (starts at 0; incremented each time "Adjust approach
|
|
|
686
705
|
|
|
687
706
|
**If iteration_count < MAX_ITERATIONS:**
|
|
688
707
|
|
|
689
|
-
**Sprint-checker malfunction guard (BLOCKER-class
|
|
708
|
+
**Sprint-checker malfunction guard (BLOCKER-class):**
|
|
690
709
|
|
|
691
710
|
Before parsing issues, verify the checker actually invoked tools. The checker MUST exhibit at least one of these evidence markers in its return:
|
|
692
711
|
|
|
@@ -695,11 +714,11 @@ Before parsing issues, verify the checker actually invoked tools. The checker MU
|
|
|
695
714
|
- At least one `path:` field in any block (e.g. `path: src/components/Foo.tsx:42`)
|
|
696
715
|
- A summary line of the form `Verified N of M files` or `Checked N symbols`
|
|
697
716
|
|
|
698
|
-
If NONE of these evidence markers are present, the checker malfunctioned (returned narrative without invoking tools
|
|
717
|
+
If NONE of these evidence markers are present, the checker malfunctioned (returned narrative without invoking tools). BLOCK execution:
|
|
699
718
|
|
|
700
719
|
```
|
|
701
720
|
Display: "Sprint-checker returned without evidence of tool use — likely
|
|
702
|
-
malfunctioned (
|
|
721
|
+
malfunctioned (returned narrative without tool use). Refusing to advance the plan
|
|
703
722
|
on unverified output. Re-run /rcode-plan or inspect the agent."
|
|
704
723
|
Halt the workflow with a non-zero exit signal.
|
|
705
724
|
```
|
|
@@ -771,7 +790,7 @@ Display: `Max iterations reached. {N} issues remain:` + issue list
|
|
|
771
790
|
|
|
772
791
|
Offer: 1) Force proceed, 2) Provide guidance and retry, 3) Abandon
|
|
773
792
|
|
|
774
|
-
## 12.5. Wave Parallelism File-Overlap Check
|
|
793
|
+
## 12.5. Wave Parallelism File-Overlap Check
|
|
775
794
|
|
|
776
795
|
Before declaring plans ready, validate the wave-parallelism rule the planner declares: **same wave + overlapping `files_modified` = sequential, not parallel**. If two plans share `depends_on` (same wave) and both list the same file in `files_modified`, the planner should have marked the later one `sequential: true`. Catch the cases where it didn't.
|
|
777
796
|
|
|
@@ -814,7 +833,7 @@ The CLI helper returns a JSON report:
|
|
|
814
833
|
|
|
815
834
|
**If `conflicts` is empty:** Display `Wave parallelism: ✓ no file-overlap conflicts.` and proceed.
|
|
816
835
|
|
|
817
|
-
This closes the gap
|
|
836
|
+
This closes the wave-overlap gap — the rule was stated in `rcode-planner.md` but not enforced. Now it's enforced automatically.
|
|
818
837
|
|
|
819
838
|
## 13. Requirements Coverage Gate
|
|
820
839
|
|
|
@@ -948,7 +967,7 @@ Route to `<offer_next>` (existing behavior).
|
|
|
948
967
|
</process>
|
|
949
968
|
|
|
950
969
|
<banner_emission_gate>
|
|
951
|
-
|
|
970
|
+
The success banner is gated on real verification, not vibes.
|
|
952
971
|
Before emitting `PLANNED ✓`, confirm one of these is true:
|
|
953
972
|
|
|
954
973
|
1. A passing CHECK.md exists at `${PHASE_DIR}/*-CHECK.md` from rcode-sprint-checker
|
|
@@ -82,6 +82,8 @@ Error: rcode-tools init failed. Verify .rcode/ is installed and state.json is va
|
|
|
82
82
|
|
|
83
83
|
Read from init: `phase_dir`, `phase_number`, `padded_phase`.
|
|
84
84
|
|
|
85
|
+
If `phase_dir` is null or empty, STOP and tell the user: "Phase directory not found on disk. Create the phase directory under `.planning/phases/` first (e.g. `mkdir -p .planning/phases/1-name`), then re-run."
|
|
86
|
+
|
|
85
87
|
Then read:
|
|
86
88
|
1. `.planning/PROJECT.md` (first 80 lines — project context)
|
|
87
89
|
2. Phase section from `.planning/ROADMAP.md`
|
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
# Workflow: rcode-scaffold-project
|
|
2
2
|
|
|
3
3
|
<purpose>
|
|
4
|
-
Scaffold a new project from the official rcode template repo
|
|
4
|
+
Scaffold a new project from the official rcode template repo, or initialize rcode
|
|
5
|
+
in an existing project (brownfield mode). Delegates to the rcode-scaffold-project skill.
|
|
6
|
+
|
|
7
|
+
Use `--here` (or say "scaffold here") to add rcode to an existing directory without
|
|
8
|
+
cloning the template. Existing files are never modified in brownfield mode.
|
|
5
9
|
</purpose>
|
|
6
10
|
|
|
7
11
|
## Execution
|
|
@@ -79,7 +79,7 @@ Extract counts:
|
|
|
79
79
|
|
|
80
80
|
### Step 5a — Prefer measured totals from cost.jsonl
|
|
81
81
|
|
|
82
|
-
If the `cost-track` hook
|
|
82
|
+
If the `cost-track` hook is enabled, it appends one usage record per
|
|
83
83
|
response to `.rcode/telemetry/cost.jsonl`. Check for it first:
|
|
84
84
|
|
|
85
85
|
```bash
|
package/rcode/workflows/ship.md
CHANGED
|
@@ -34,6 +34,45 @@ the plan → execute → verify → **ship** loop.
|
|
|
34
34
|
```
|
|
35
35
|
</purpose>
|
|
36
36
|
|
|
37
|
+
<prerequisites>
|
|
38
|
+
|
|
39
|
+
**Required before running `/rcode-ship`:**
|
|
40
|
+
|
|
41
|
+
1. **Git remote configured** — `git remote -v` must list at least one remote (typically `origin`). Without a remote, the push and PR steps will fail.
|
|
42
|
+
2. **`gh` CLI authenticated** — `gh auth status` must succeed. Without this, PR creation will fail.
|
|
43
|
+
3. **Clean working tree** — no uncommitted changes (`git status --short` returns nothing).
|
|
44
|
+
4. **On a feature branch** — not on `main` or `develop` directly.
|
|
45
|
+
5. **Verification passed** — `/rcode-verify-phase <phase>` must have run and produced a VERIFICATION.md with `status: passed`.
|
|
46
|
+
|
|
47
|
+
</prerequisites>
|
|
48
|
+
|
|
49
|
+
<warning>
|
|
50
|
+
|
|
51
|
+
**If your workspace has no git remote or `gh` is not authenticated, the push and PR steps will fail.**
|
|
52
|
+
|
|
53
|
+
Check before running:
|
|
54
|
+
```bash
|
|
55
|
+
git remote -v # must list at least one remote
|
|
56
|
+
gh auth status # must exit 0
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
**Manual fallbacks if these are missing:**
|
|
60
|
+
|
|
61
|
+
- **No remote:** Add one with `git remote add origin <repo-url>`, then re-run `/rcode-ship`. Or push manually:
|
|
62
|
+
```bash
|
|
63
|
+
git push origin <branch>
|
|
64
|
+
```
|
|
65
|
+
Then open a PR via the GitHub web UI at `https://github.com/<owner>/<repo>/compare/<branch>`.
|
|
66
|
+
|
|
67
|
+
- **`gh` not authenticated:** Run `gh auth login` to authenticate, then re-run `/rcode-ship`. Or create the PR directly at:
|
|
68
|
+
```
|
|
69
|
+
https://github.com/<owner>/<repo>/compare/<branch>
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
- **Git worktree context:** If `.git` is a file (not a directory), you are in a worktree. Remotes are shared with the main repo — run `git remote -v` from the main repo root to verify. If the main repo has `origin`, the worktree inherits it automatically.
|
|
73
|
+
|
|
74
|
+
</warning>
|
|
75
|
+
|
|
37
76
|
<required_reading>
|
|
38
77
|
Read all files referenced by the invoking prompt's execution_context before starting.
|
|
39
78
|
</required_reading>
|
|
@@ -73,6 +73,33 @@ If `$ARGUMENTS` contains `--help` or `-h`:
|
|
|
73
73
|
|
|
74
74
|
STOP — do not proceed.
|
|
75
75
|
|
|
76
|
+
## Preflight — Project-status check
|
|
77
|
+
|
|
78
|
+
```bash
|
|
79
|
+
PROJECT_STATUS=$(node .rcode/bin/rcode-tools.cjs project-status 2>/dev/null || echo uninitialized)
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
If `PROJECT_STATUS` is `uninstalled`, `uninitialized`, or `stub`:
|
|
83
|
+
|
|
84
|
+
```
|
|
85
|
+
Project not initialized. Run /rcode-init first (or /rcode-new-project for a greenfield project), then return here.
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
Stop. Do not proceed until `project-status` returns `real`.
|
|
89
|
+
|
|
90
|
+
## Preflight — Dependency check
|
|
91
|
+
|
|
92
|
+
If a `package.json` exists in the project root but `node_modules/` is absent or empty, emit a WARNING before planning begins:
|
|
93
|
+
|
|
94
|
+
```
|
|
95
|
+
⚠ WARNING: package.json found but node_modules/ is missing or empty.
|
|
96
|
+
Run: pnpm install (or npm install if pnpm is not available)
|
|
97
|
+
Sprint planning can continue, but the resulting sprint tasks will fail at execution time
|
|
98
|
+
unless dependencies are installed first.
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
Do NOT auto-run the install. Emit the message and let the user decide.
|
|
102
|
+
|
|
76
103
|
## Step 1 — Load context
|
|
77
104
|
|
|
78
105
|
```bash
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# Workflow: rcode-status
|
|
2
2
|
|
|
3
3
|
<purpose>
|
|
4
|
-
Render a human-readable project status dashboard. All data comes from a single `rcode-tools progress init` call — this workflow does NOT parse ROADMAP.md, walk SUMMARY.md files, or grep state.json itself. Rendering only. See `rcode-tools.cjs` `cmdProgress` for the source-of-truth logic
|
|
4
|
+
Render a human-readable project status dashboard. All data comes from a single `rcode-tools progress init` call — this workflow does NOT parse ROADMAP.md, walk SUMMARY.md files, or grep state.json itself. Rendering only. See `rcode-tools.cjs` `cmdProgress` for the source-of-truth logic.
|
|
5
5
|
|
|
6
6
|
**SSOT:** `.rcode/state.json`. `/rcode-status` and `/rcode-progress` both call the same CLI so they cannot disagree. If the CLI reports a drift insight, surface it — do not silently compensate.
|
|
7
7
|
</purpose>
|
|
@@ -53,7 +53,7 @@ Then stop.
|
|
|
53
53
|
If `SNAPSHOT.weighted_progress > 0` but `SNAPSHOT.completed_count === 0`, display
|
|
54
54
|
the weighted bar as the primary progress indicator to avoid a misleading `0/N (0%)`.
|
|
55
55
|
|
|
56
|
-
### Milestone health
|
|
56
|
+
### Milestone health
|
|
57
57
|
|
|
58
58
|
After the main dashboard, call `rcode-tools milestone-health` and surface
|
|
59
59
|
a gauge when the milestone is full:
|
|
@@ -108,7 +108,7 @@ Phases:
|
|
|
108
108
|
|
|
109
109
|
If a phase number starts with `999.`, render with a `🅿` marker and the label `(parking lot)`.
|
|
110
110
|
|
|
111
|
-
## Step 4 — Insights
|
|
111
|
+
## Step 4 — Insights
|
|
112
112
|
|
|
113
113
|
If `SNAPSHOT.insights[]` is non-empty, print above the Next Up section:
|
|
114
114
|
|
package/server/dashboard.js
CHANGED
|
@@ -31,7 +31,7 @@ const { spawn } = require('child_process');
|
|
|
31
31
|
const CLIENT_DIR = path.join(__dirname, 'lib', 'html', 'client');
|
|
32
32
|
|
|
33
33
|
const { scanState } = require('./lib/scanner');
|
|
34
|
-
const { handleApiState, handleApiFiles, handleApiFile, handleApiHierarchy, handleApiMemory } = require('./lib/api');
|
|
34
|
+
const { handleApiState, handleApiFiles, handleApiFile, handleApiHierarchy, handleApiMemory, handleApiAgents } = require('./lib/api');
|
|
35
35
|
const { renderHtml } = require('./lib/html/shell');
|
|
36
36
|
|
|
37
37
|
// ---------- Configuration ----------
|
|
@@ -60,7 +60,20 @@ function loadOrchToken() {
|
|
|
60
60
|
const ORCH_TOKEN = loadOrchToken();
|
|
61
61
|
|
|
62
62
|
// ---------- HTTP Server ----------
|
|
63
|
+
// Every request runs through a try/catch so an unanticipated throw inside a
|
|
64
|
+
// handler (e.g. a pathological .planning tree in the scanner) returns a 500
|
|
65
|
+
// instead of crashing the whole server process.
|
|
63
66
|
const server = http.createServer((req, res) => {
|
|
67
|
+
try {
|
|
68
|
+
handleRequest(req, res);
|
|
69
|
+
} catch (err) {
|
|
70
|
+
console.error('[dashboard] request handler failed:', err && err.stack || err);
|
|
71
|
+
if (!res.headersSent) res.writeHead(500, { 'Content-Type': 'text/plain' });
|
|
72
|
+
res.end('Internal server error');
|
|
73
|
+
}
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
function handleRequest(req, res) {
|
|
64
77
|
const url = req.url || '/';
|
|
65
78
|
|
|
66
79
|
if (url === '/health') {
|
|
@@ -79,6 +92,11 @@ const server = http.createServer((req, res) => {
|
|
|
79
92
|
return;
|
|
80
93
|
}
|
|
81
94
|
|
|
95
|
+
if (url === '/api/agents') {
|
|
96
|
+
handleApiAgents(req, res, PROJECT_ROOT);
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
|
|
82
100
|
if (url.startsWith('/api/file')) {
|
|
83
101
|
handleApiFile(req, res, PROJECT_ROOT);
|
|
84
102
|
return;
|
|
@@ -104,10 +122,12 @@ const server = http.createServer((req, res) => {
|
|
|
104
122
|
|
|
105
123
|
if (url.startsWith('/js/')) {
|
|
106
124
|
const name = url.slice(4).split('?')[0];
|
|
107
|
-
// Allow
|
|
108
|
-
// while still rejecting traversal
|
|
109
|
-
//
|
|
110
|
-
|
|
125
|
+
// Allow nested subdirectories (e.g. components/App.js, views/Foo.js,
|
|
126
|
+
// components/dashboard/ProgressDonut.js) while still rejecting traversal.
|
|
127
|
+
// The regex limits each segment to word chars, dots, and hyphens; the
|
|
128
|
+
// resolved-path check below is the real traversal guard (a `..` segment
|
|
129
|
+
// would pass this pattern but fail the CLIENT_DIR containment check).
|
|
130
|
+
if (!/^(?:[\w.-]+\/)*[\w.-]+\.js$/.test(name)) { res.writeHead(404); res.end('Not found'); return; }
|
|
111
131
|
// Defense-in-depth: resolved path must stay inside CLIENT_DIR even after
|
|
112
132
|
// any OS-level resolution (handles encoded traversal the regex might miss).
|
|
113
133
|
const resolved = path.resolve(CLIENT_DIR, name);
|
|
@@ -135,7 +155,7 @@ const server = http.createServer((req, res) => {
|
|
|
135
155
|
|
|
136
156
|
res.writeHead(404);
|
|
137
157
|
res.end('Not found');
|
|
138
|
-
}
|
|
158
|
+
}
|
|
139
159
|
|
|
140
160
|
server.listen(PORT, '127.0.0.1', () => {
|
|
141
161
|
console.log(`\n🕌 Majlis (مجلس) — rcode Dashboard`);
|
|
@@ -144,7 +164,6 @@ server.listen(PORT, '127.0.0.1', () => {
|
|
|
144
164
|
console.log(` URL: http://localhost:${PORT}`);
|
|
145
165
|
console.log(` Scanning: ${RCODE_DIR}`);
|
|
146
166
|
console.log(` Refresh: 30s soft poll`);
|
|
147
|
-
console.log(` Keys: R=refresh 1-9=views F=filter`);
|
|
148
167
|
console.log(` Stop: kill $(ss -ltnp 'sport = :${PORT}' | awk 'NR>1{match($6,/pid=([0-9]+)/,m); print m[1]}')`);
|
|
149
168
|
console.log(`━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n`);
|
|
150
169
|
});
|
package/server/lib/api.js
CHANGED
|
@@ -8,7 +8,8 @@ const { scanState, scanMemoryBank } = require('./scanner');
|
|
|
8
8
|
function handleApiState(req, res, rcodeDir) {
|
|
9
9
|
const state = scanState(rcodeDir);
|
|
10
10
|
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
11
|
-
|
|
11
|
+
// Compact JSON — pretty-printing roughly doubled the polled payload.
|
|
12
|
+
res.end(JSON.stringify(state));
|
|
12
13
|
}
|
|
13
14
|
|
|
14
15
|
function handleApiFiles(req, res, projectRoot) {
|
|
@@ -192,13 +193,70 @@ function handleApiHierarchy(req, res, rcodeDir) {
|
|
|
192
193
|
})),
|
|
193
194
|
};
|
|
194
195
|
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
195
|
-
res.end(JSON.stringify(hierarchy
|
|
196
|
+
res.end(JSON.stringify(hierarchy));
|
|
196
197
|
}
|
|
197
198
|
|
|
198
199
|
function handleApiMemory(req, res, rcodeDir) {
|
|
199
200
|
const memory = scanMemoryBank(rcodeDir);
|
|
200
201
|
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
201
|
-
res.end(JSON.stringify(memory
|
|
202
|
+
res.end(JSON.stringify(memory));
|
|
202
203
|
}
|
|
203
204
|
|
|
204
|
-
|
|
205
|
+
// Parse the keys we surface on agent cards out of an agent definition's
|
|
206
|
+
// YAML frontmatter. Deliberately not a YAML parser: top-level `key: value`
|
|
207
|
+
// scalar lines are read directly; for `description` (usually a `|` block
|
|
208
|
+
// scalar) the first two indented lines are captured as a card-sized summary.
|
|
209
|
+
function parseAgentFrontmatter(raw) {
|
|
210
|
+
const meta = { name: null, model: null, tools: [], color: null, description: null };
|
|
211
|
+
if (!raw.startsWith('---')) return meta;
|
|
212
|
+
const end = raw.indexOf('\n---', 3);
|
|
213
|
+
if (end === -1) return meta;
|
|
214
|
+
let inDescription = false;
|
|
215
|
+
const descLines = [];
|
|
216
|
+
for (const line of raw.slice(3, end).split('\n')) {
|
|
217
|
+
const m = line.match(/^([A-Za-z][\w-]*):\s*(.*)$/);
|
|
218
|
+
if (m) {
|
|
219
|
+
inDescription = false;
|
|
220
|
+
const key = m[1].toLowerCase();
|
|
221
|
+
const value = m[2].trim();
|
|
222
|
+
if (key === 'description') {
|
|
223
|
+
if (value && value !== '|' && value !== '>') descLines.push(value);
|
|
224
|
+
else inDescription = true;
|
|
225
|
+
continue;
|
|
226
|
+
}
|
|
227
|
+
if (!value || value === '|' || value === '>') continue;
|
|
228
|
+
if (key === 'name') meta.name = value;
|
|
229
|
+
if (key === 'model') meta.model = value;
|
|
230
|
+
if (key === 'color') meta.color = value;
|
|
231
|
+
if (key === 'tools') meta.tools = value.split(',').map(t => t.trim()).filter(Boolean);
|
|
232
|
+
} else if (inDescription && descLines.length < 2 && /^\s+\S/.test(line)) {
|
|
233
|
+
descLines.push(line.trim());
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
meta.description = descLines.join(' ') || null;
|
|
237
|
+
return meta;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
// Read-only roster metadata for the Agents view. Scans the fixed
|
|
241
|
+
// rcode/agents/ directory (no user-supplied paths — nothing to contain) and
|
|
242
|
+
// returns one small frontmatter summary per agent .md file. Full prompt
|
|
243
|
+
// bodies are NOT included; the client fetches those lazily per agent via the
|
|
244
|
+
// existing /api/file handler when a card is opened.
|
|
245
|
+
function handleApiAgents(req, res, projectRoot) {
|
|
246
|
+
const agentsDir = path.join(projectRoot, 'rcode', 'agents');
|
|
247
|
+
let entries = [];
|
|
248
|
+
try { entries = fs.readdirSync(agentsDir, { withFileTypes: true }); }
|
|
249
|
+
catch { /* no agents dir — return an empty roster */ }
|
|
250
|
+
const agents = [];
|
|
251
|
+
for (const e of entries) {
|
|
252
|
+
if (!e.isFile() || !e.name.endsWith('.md')) continue;
|
|
253
|
+
let raw;
|
|
254
|
+
try { raw = fs.readFileSync(path.join(agentsDir, e.name), 'utf8'); }
|
|
255
|
+
catch { continue; }
|
|
256
|
+
agents.push({ file: e.name, ...parseAgentFrontmatter(raw) });
|
|
257
|
+
}
|
|
258
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
259
|
+
res.end(JSON.stringify(agents));
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
module.exports = { handleApiState, handleApiFiles, handleApiFile, handleApiHierarchy, handleApiMemory, handleApiAgents };
|
|
@@ -3,25 +3,29 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Previously lived in shell.js:17-36 as a server-rendered array.
|
|
5
5
|
* Now exported as a pure ESM constant so AgentsView can render it.
|
|
6
|
+
*
|
|
7
|
+
* `file` is the agent's definition under rcode/agents/ — fetched lazily by
|
|
8
|
+
* AgentsView when a card is opened. null = no prompt file on disk (system
|
|
9
|
+
* entries like Raees/Majlis/Diwan are skills, not agent definitions).
|
|
6
10
|
*/
|
|
7
11
|
|
|
8
12
|
export const AGENTS = [
|
|
9
|
-
{ name: 'Sadiq Damani', arabic: 'صادق', role: 'Director of Strategy', real: true, type: 'leadership' },
|
|
10
|
-
{ name: 'Waleed Al Harthi', arabic: 'وليد', role: 'CTO', real: true, type: 'leadership' },
|
|
11
|
-
{ name: 'Ahmed Al Hassani', arabic: 'أحمد الحسني', role: 'Technology & Development Director', real: true, type: 'leadership' },
|
|
12
|
-
{ name: 'Nasser', arabic: 'ناصر', role: 'Engineering Manager', real: true, type: 'leadership' },
|
|
13
|
-
{ name: 'Hussain', arabic: 'حسين', role: 'PM + Scrum Master', type: 'product' },
|
|
14
|
-
{ name: 'Layla', arabic: 'ليلى', role: 'Lead UX Designer', type: 'design' },
|
|
15
|
-
{ name: 'Zahra', arabic: 'زهرة', role: 'Branding & Creative Director', type: 'design' },
|
|
16
|
-
{ name: 'Omar', arabic: 'عمر', role: 'Full-Stack Engineer', type: 'engineering' },
|
|
17
|
-
{ name: 'Haitham Al Khamiyasi', arabic: 'هيثم', role: 'Senior Frontend', real: true, type: 'engineering' },
|
|
18
|
-
{ name: 'Yousef', arabic: 'يوسف', role: 'Senior Backend', type: 'engineering' },
|
|
19
|
-
{ name: 'Zayd', arabic: 'زيد', role: 'ML Engineer', type: 'engineering' },
|
|
20
|
-
{ name: 'Fatima', arabic: 'فاطمة', role: 'QA Lead', type: 'quality' },
|
|
21
|
-
{ name: 'Khalid', arabic: 'خالد', role: 'DevOps', type: 'engineering' },
|
|
22
|
-
{ name: 'Noor', arabic: 'نور', role: 'Scribe', type: 'support' },
|
|
23
|
-
{ name: 'Mariam', arabic: 'مريم', role: 'Marketing Lead', type: 'product' },
|
|
24
|
-
{ name: 'Raees', arabic: 'رئيس', role: 'Orchestration Director', type: 'system' },
|
|
25
|
-
{ name: 'Majlis', arabic: 'مجلس', role: 'Consulting Council', type: 'system' },
|
|
26
|
-
{ name: 'Diwan', arabic: 'ديوان', role: 'Dashboard Registry', type: 'system' },
|
|
13
|
+
{ name: 'Sadiq Damani', arabic: 'صادق', role: 'Director of Strategy', real: true, type: 'leadership', file: 'rcode-sadiq.md' },
|
|
14
|
+
{ name: 'Waleed Al Harthi', arabic: 'وليد', role: 'CTO', real: true, type: 'leadership', file: 'rcode-waleed.md' },
|
|
15
|
+
{ name: 'Ahmed Al Hassani', arabic: 'أحمد الحسني', role: 'Technology & Development Director', real: true, type: 'leadership', file: 'rcode-ahmed.md' },
|
|
16
|
+
{ name: 'Nasser', arabic: 'ناصر', role: 'Engineering Manager', real: true, type: 'leadership', file: 'rcode-nasser.md' },
|
|
17
|
+
{ name: 'Hussain', arabic: 'حسين', role: 'PM + Scrum Master', type: 'product', file: 'rcode-hussain-pm.md' },
|
|
18
|
+
{ name: 'Layla', arabic: 'ليلى', role: 'Lead UX Designer', type: 'design', file: 'rcode-layla.md' },
|
|
19
|
+
{ name: 'Zahra', arabic: 'زهرة', role: 'Branding & Creative Director', type: 'design', file: 'rcode-zahra.md' },
|
|
20
|
+
{ name: 'Omar', arabic: 'عمر', role: 'Full-Stack Engineer', type: 'engineering', file: 'rcode-omar.md' },
|
|
21
|
+
{ name: 'Haitham Al Khamiyasi', arabic: 'هيثم', role: 'Senior Frontend', real: true, type: 'engineering', file: 'rcode-haitham.md' },
|
|
22
|
+
{ name: 'Yousef', arabic: 'يوسف', role: 'Senior Backend', type: 'engineering', file: 'rcode-yousef.md' },
|
|
23
|
+
{ name: 'Zayd', arabic: 'زيد', role: 'ML Engineer', type: 'engineering', file: 'rcode-zayd.md' },
|
|
24
|
+
{ name: 'Fatima', arabic: 'فاطمة', role: 'QA Lead', type: 'quality', file: 'rcode-fatima.md' },
|
|
25
|
+
{ name: 'Khalid', arabic: 'خالد', role: 'DevOps', type: 'engineering', file: 'rcode-khalid.md' },
|
|
26
|
+
{ name: 'Noor', arabic: 'نور', role: 'Scribe', type: 'support', file: 'rcode-noor.md' },
|
|
27
|
+
{ name: 'Mariam', arabic: 'مريم', role: 'Marketing Lead', type: 'product', file: 'rcode-mariam.md' },
|
|
28
|
+
{ name: 'Raees', arabic: 'رئيس', role: 'Orchestration Director', type: 'system', file: null },
|
|
29
|
+
{ name: 'Majlis', arabic: 'مجلس', role: 'Consulting Council', type: 'system', file: null },
|
|
30
|
+
{ name: 'Diwan', arabic: 'ديوان', role: 'Dashboard Registry', type: 'system', file: null },
|
|
27
31
|
];
|
|
@@ -11,5 +11,8 @@ import { App } from './components/App.js';
|
|
|
11
11
|
|
|
12
12
|
const root = document.getElementById('app-root');
|
|
13
13
|
if (root) {
|
|
14
|
+
// Drop the SSR loading shell — Preact diffs against existing children,
|
|
15
|
+
// so the spinner must be gone before the first render.
|
|
16
|
+
root.textContent = '';
|
|
14
17
|
render(html`<${App}/>`, root);
|
|
15
18
|
}
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AgentCard — card, avatar, chips, and detail drawer for the Agents view.
|
|
3
|
+
*
|
|
4
|
+
* Extracted from AgentsView so the view module stays focused on grouping,
|
|
5
|
+
* search, and fetch state. Per-role accent colors are driven by a single
|
|
6
|
+
* `agent-accent--<type>` class on the card/drawer root: it sets the
|
|
7
|
+
* --agent-accent custom property that the avatar, role badge, and hover
|
|
8
|
+
* border all read (see the AGENTS VIEW block at the end of css.js).
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { html } from '../preact.js';
|
|
12
|
+
import { setState } from '../store.js';
|
|
13
|
+
import { pressable, showToast } from './shared.js';
|
|
14
|
+
import { renderMd } from '../util.js';
|
|
15
|
+
|
|
16
|
+
const MAX_CARD_TOOL_CHIPS = 4;
|
|
17
|
+
|
|
18
|
+
/** "Sadiq Damani" -> "SD", "Hussain" -> "H". */
|
|
19
|
+
function initialsOf(name) {
|
|
20
|
+
return name.split(/\s+/).filter(Boolean).slice(0, 2).map(w => w[0]).join('').toUpperCase();
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/** Per-role accent class — types map 1:1 to the CSS accent variants. */
|
|
24
|
+
export function accentClass(agent) {
|
|
25
|
+
return 'agent-accent--' + (agent.type || 'system');
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// ---- Avatar circle with initials ----
|
|
29
|
+
function Avatar({ agent, large }) {
|
|
30
|
+
return html`<span class=${'agent-avatar' + (large ? ' agent-avatar--lg' : '')} aria-hidden="true">${initialsOf(agent.name)}</span>`;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// ---- Metadata chips (model + tools), shared by card and drawer ----
|
|
34
|
+
export function MetaChips({ meta, maxTools }) {
|
|
35
|
+
if (!meta) return null;
|
|
36
|
+
const tools = meta.tools || [];
|
|
37
|
+
const shown = maxTools ? tools.slice(0, maxTools) : tools;
|
|
38
|
+
const extra = tools.length - shown.length;
|
|
39
|
+
if (!meta.model && !shown.length) return null;
|
|
40
|
+
return html`
|
|
41
|
+
<div class="agent-chips">
|
|
42
|
+
${meta.model ? html`<span class="agent-chip agent-chip--model">${meta.model}</span>` : null}
|
|
43
|
+
${shown.map(t => html`<span class="agent-chip" key=${t}>${t}</span>`)}
|
|
44
|
+
${extra > 0 ? html`<span class="agent-chip agent-chip--more">+${extra}</span>` : null}
|
|
45
|
+
</div>
|
|
46
|
+
`;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// ---- Single agent card ----
|
|
50
|
+
export function AgentCard({ agent, meta, onOpen }) {
|
|
51
|
+
return html`
|
|
52
|
+
<div class=${'agent-card ' + accentClass(agent)} ...${pressable(() => onOpen(agent))}>
|
|
53
|
+
<div class="agent-card-top">
|
|
54
|
+
<${Avatar} agent=${agent} />
|
|
55
|
+
<div class="agent-card-id">
|
|
56
|
+
<div class="agent-card-name">
|
|
57
|
+
${agent.name}
|
|
58
|
+
${agent.real ? html`<span class="real-badge">real</span>` : null}
|
|
59
|
+
</div>
|
|
60
|
+
<span class="role-badge">${agent.role}</span>
|
|
61
|
+
</div>
|
|
62
|
+
<span class="agent-card-arabic">${agent.arabic}</span>
|
|
63
|
+
</div>
|
|
64
|
+
${meta && meta.description ? html`<p class="agent-card-desc">${meta.description}</p>` : null}
|
|
65
|
+
<${MetaChips} meta=${meta} maxTools=${MAX_CARD_TOOL_CHIPS} />
|
|
66
|
+
</div>
|
|
67
|
+
`;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// ---- Detail drawer ----
|
|
71
|
+
export function AgentDrawer({ agent, meta, prompt, onClose }) {
|
|
72
|
+
const filePath = agent.file ? 'rcode/agents/' + agent.file : null;
|
|
73
|
+
|
|
74
|
+
function copyPath() {
|
|
75
|
+
navigator.clipboard.writeText(filePath).then(() => {
|
|
76
|
+
showToast('Path copied!');
|
|
77
|
+
}).catch(() => {});
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function openInFiles() {
|
|
81
|
+
setState({ requestedFile: filePath });
|
|
82
|
+
window.location.hash = 'files';
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
let body;
|
|
86
|
+
if (!agent.file) {
|
|
87
|
+
body = html`<div class="agent-drawer-empty">No prompt file on disk — this is a system entry without an agent definition.</div>`;
|
|
88
|
+
} else if (prompt.loading) {
|
|
89
|
+
body = html`
|
|
90
|
+
<div class="skeleton"></div>
|
|
91
|
+
<div class="agent-drawer-skeleton skeleton"></div>
|
|
92
|
+
`;
|
|
93
|
+
} else if (prompt.error) {
|
|
94
|
+
body = html`<div class="agent-drawer-error">${prompt.error}</div>`;
|
|
95
|
+
} else if (prompt.text) {
|
|
96
|
+
body = html`<div class="md-render" dangerouslySetInnerHTML=${{ __html: renderMd(prompt.text) }} />`;
|
|
97
|
+
} else {
|
|
98
|
+
body = null;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
return html`
|
|
102
|
+
<div class="agent-drawer-backdrop" onClick=${onClose}></div>
|
|
103
|
+
<aside class=${'agent-drawer ' + accentClass(agent)} role="dialog" aria-modal="true" aria-label="${agent.name} — full prompt">
|
|
104
|
+
<div class="agent-drawer-head">
|
|
105
|
+
<${Avatar} agent=${agent} large />
|
|
106
|
+
<div class="agent-drawer-titles">
|
|
107
|
+
<div class="agent-drawer-name">
|
|
108
|
+
${agent.name}
|
|
109
|
+
<span class="agent-drawer-arabic">${agent.arabic}</span>
|
|
110
|
+
${agent.real ? html`<span class="real-badge">real</span>` : null}
|
|
111
|
+
</div>
|
|
112
|
+
<span class="role-badge">${agent.role}</span>
|
|
113
|
+
<${MetaChips} meta=${meta} />
|
|
114
|
+
</div>
|
|
115
|
+
<button class="agent-drawer-close" onClick=${onClose} aria-label="Close">×</button>
|
|
116
|
+
</div>
|
|
117
|
+
${filePath ? html`
|
|
118
|
+
<div class="agent-drawer-meta">
|
|
119
|
+
<span class="agent-drawer-meta-path">${filePath}</span>
|
|
120
|
+
<button class="agent-drawer-btn" onClick=${copyPath}>Copy path</button>
|
|
121
|
+
<button class="agent-drawer-btn agent-drawer-btn--link" onClick=${openInFiles}>View in Files →</button>
|
|
122
|
+
</div>
|
|
123
|
+
` : null}
|
|
124
|
+
<div class="agent-drawer-body">${body}</div>
|
|
125
|
+
</aside>
|
|
126
|
+
`;
|
|
127
|
+
}
|