vibe-coding-master 0.2.3 → 0.2.5
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.md +6 -2
- package/dist/backend/adapters/claude-adapter.js +9 -2
- package/dist/backend/api/claude-hook-routes.js +7 -0
- package/dist/backend/server.js +2 -1
- package/dist/backend/services/app-settings-service.js +49 -1
- package/dist/backend/services/claude-hook-service.js +61 -11
- package/dist/backend/services/harness-service.js +6 -5
- package/dist/backend/services/round-service.js +40 -8
- package/dist/backend/services/session-service.js +16 -2
- package/dist/backend/templates/harness/claude-root.js +10 -0
- package/dist/backend/templates/harness/vcm-long-running-validation-skill.js +7 -2
- package/dist/shared/types/app-settings.js +17 -0
- package/dist/shared/types/session.js +37 -1
- package/dist-frontend/assets/index-BRrFbDjL.js +90 -0
- package/dist-frontend/assets/index-DQ-pEUma.css +32 -0
- package/dist-frontend/index.html +2 -2
- package/docs/full-harness-baseline.md +4 -4
- package/docs/gateway-design.md +585 -0
- package/docs/product-design.md +52 -7
- package/docs/v0.2-implementation-plan.md +78 -68
- package/docs/vcm-cc-best-practices.md +23 -4
- package/package.json +1 -1
- package/scripts/install-vcm-harness.mjs +44 -7
- package/dist-frontend/assets/index-CfqduxVB.css +0 -32
- package/dist-frontend/assets/index-DaVjpyA9.js +0 -90
package/docs/product-design.md
CHANGED
|
@@ -251,8 +251,9 @@ The old `Dirty: yes/no` label is not used. The UI uses `Working tree: clean` or
|
|
|
251
251
|
|
|
252
252
|
The default theme mode is `System`, which follows the OS/browser color-scheme preference. The entire application chrome, sidebar, forms, modals, status badges, and workspace panels must support both light and dark rendering. Embedded terminals keep their terminal-native dark styling.
|
|
253
253
|
|
|
254
|
-
When `Flow pause alert` is on, VCM plays a short, soft, two-note local chime after a role flow stops advancing. If the flow lasted less than
|
|
254
|
+
When `Flow pause alert` is on, VCM plays a short, soft, two-note local chime after a role flow stops advancing. If the flow lasted less than 2 minutes, the chime plays 3 times, 1.4 seconds apart, and stops. If the flow lasted 2 minutes or longer, VCM shows an in-app alert dialog and repeats the chime until the user confirms the dialog. The alert sound must reuse one browser audio context after user activation instead of creating a fresh context for each repeat, because Safari can block repeated timer-driven playback when every repeat looks like a new autoplay attempt.
|
|
255
255
|
`Try alert` must work even when no flow has just paused so the user can verify browser sound and notification behavior.
|
|
256
|
+
Safari may still require the user to manually set `Safari > Website Settings > Auto-Play > Allow All Auto-Play`; Chrome is the recommended browser for reliable repeated alert sound.
|
|
256
257
|
|
|
257
258
|
There is no separate `Pause orchestration` or `Resume orchestration` control in the GUI. The current product model is one on/off toggle in the role console toolbar.
|
|
258
259
|
|
|
@@ -334,10 +335,11 @@ Task-level round state:
|
|
|
334
335
|
- `Stop` moves the round into a 10 second settling window.
|
|
335
336
|
- A new `UserPromptSubmit` inside the window continues the same round.
|
|
336
337
|
- If no new prompt is accepted before the deadline, the round becomes `paused`.
|
|
337
|
-
- The
|
|
338
|
+
- The pause transition is timer-driven from the `Stop` event. Round-state reads do not end a round.
|
|
339
|
+
- Before pausing, VCM checks `.ai/vcm/handoffs/messages`; if a pending route message exists and can be delivered, VCM retries delivery and extends the settling window instead of alerting.
|
|
338
340
|
- The same round state stores total round count, prompt-submit count, stop count, and Claude Code active runtime. Active runtime is measured only between `UserPromptSubmit` and `Stop`, not during the settling window.
|
|
339
341
|
|
|
340
|
-
The frontend polls this task-level round state and deduplicates
|
|
342
|
+
The frontend polls this task-level round state and deduplicates each paused round so the same paused state does not alert on every poll. Flow duration is measured from the first `UserPromptSubmit` to `pausedAt`, falling back to the last `Stop` when needed. Pauses under 2 minutes trigger the weak 3-chime reminder at 1.4 second intervals. Pauses at or above 2 minutes trigger the strong alert dialog and repeating sound until confirmation.
|
|
341
343
|
|
|
342
344
|
## 9. Session Lifecycle
|
|
343
345
|
|
|
@@ -477,7 +479,7 @@ Default route policy:
|
|
|
477
479
|
|
|
478
480
|
Stop-triggered scan:
|
|
479
481
|
|
|
480
|
-
- VCM injects Claude Code `UserPromptSubmit` and `
|
|
482
|
+
- VCM injects Claude Code `UserPromptSubmit`, `Stop`, and `PermissionRequest` hooks into `.claude/settings.json`.
|
|
481
483
|
- The hooks do not call `vcmctl`; they POST directly to the local VCM backend.
|
|
482
484
|
- When any role stops, VCM marks that role idle, then scans pending route files.
|
|
483
485
|
- VCM scans the stopped role's outgoing files and also any pending files targeting newly idle roles, so messages that were blocked by a busy target can be delivered after that target stops.
|
|
@@ -511,11 +513,14 @@ VCM Harness injects Claude Code hooks into `.claude/settings.json`:
|
|
|
511
513
|
|
|
512
514
|
- `UserPromptSubmit`: posts directly to the VCM backend, marks the role running, and confirms any matching VCM message by recording `acceptedAt`
|
|
513
515
|
- `Stop`: posts directly to the VCM backend, marks the role idle, and triggers pending route-file dispatch
|
|
516
|
+
- `PermissionRequest`: posts directly to the VCM backend and prints the backend response to stdout so Claude Code can consume an allow decision when the local VCM setting is enabled
|
|
514
517
|
|
|
515
518
|
VCM uses `UserPromptSubmit` as the Claude Code acceptance signal. A successful PTY write only proves VCM delivered text to the embedded terminal; `UserPromptSubmit` proves Claude Code accepted the prompt.
|
|
516
519
|
|
|
517
520
|
The injected role rules require asynchronous file messaging: after writing or updating a route file, the role must end the current Claude Code turn and wait for VCM to deliver a later reply. Roles should use `.claude/skills/vcm-route-message/SKILL.md` to author route files. Roles must not poll files, start shell loops, keep the turn open waiting for another role to answer, paste directly into another role terminal, or use Claude Code Task/Subagent for VCM role delegation.
|
|
518
521
|
|
|
522
|
+
Roles must not start background jobs. The only allowed background job is `.ai/tools/run-long-check` when used through `vcm-long-running-validation`; `watch-job` rejects timeouts over 60 minutes.
|
|
523
|
+
|
|
519
524
|
There is no `vcmctl` in the target design. Hook entrypoints are direct HTTP from Claude Code hooks to the local VCM backend.
|
|
520
525
|
|
|
521
526
|
## 13. Translation
|
|
@@ -630,7 +635,32 @@ Translation panel `Auto-send` is separate from task `Auto orchestration`:
|
|
|
630
635
|
|
|
631
636
|
Task `Auto orchestration` is a compact selected/unselected button in the role console toolbar. `Translate` is a global task header toggle next to `Close Task`; it opens/closes the translation split for all role consoles, so switching roles keeps the same translation setting.
|
|
632
637
|
|
|
633
|
-
## 14.
|
|
638
|
+
## 14. Mobile Gateway
|
|
639
|
+
|
|
640
|
+
VCM Gateway is a mobile Weixin DM bridge to the local desktop VCM instance.
|
|
641
|
+
|
|
642
|
+
Gateway product rules:
|
|
643
|
+
|
|
644
|
+
- DM only; group chat is not supported.
|
|
645
|
+
- One mobile Weixin DM identity binds to one desktop VCM instance.
|
|
646
|
+
- Binding is not tied to one project or one task.
|
|
647
|
+
- The bound phone can select among the projects and tasks available to the
|
|
648
|
+
desktop VCM instance.
|
|
649
|
+
- Plain mobile text is sent only to the current task's `project-manager`.
|
|
650
|
+
- Gateway never sends directly to `architect`, `coder`, or `reviewer`.
|
|
651
|
+
- Gateway can push PM assistant replies to Weixin whenever gateway is enabled,
|
|
652
|
+
even if that PM turn was started from desktop VCM.
|
|
653
|
+
- When gateway translation is enabled, mobile Chinese input is translated to
|
|
654
|
+
English before PM receives it, and PM English replies are translated to
|
|
655
|
+
Chinese before Weixin receives them.
|
|
656
|
+
- The PM prompt does not include the original Chinese text.
|
|
657
|
+
- There is no multi-user allowlist. The security model is one bound DM identity.
|
|
658
|
+
|
|
659
|
+
The first channel is Tencent iLink Bot API / Weixin DM. VCM uses QR login,
|
|
660
|
+
`getupdates` long polling, and `sendmessage` text replies. Gateway details and
|
|
661
|
+
implementation plan live in `docs/gateway-design.md`.
|
|
662
|
+
|
|
663
|
+
## 15. Local State
|
|
634
664
|
|
|
635
665
|
App-level settings:
|
|
636
666
|
|
|
@@ -641,9 +671,21 @@ App-level settings:
|
|
|
641
671
|
Stored app-level settings include:
|
|
642
672
|
|
|
643
673
|
- UI theme mode: `system`, `light`, or `dark`
|
|
674
|
+
- flow pause alert preference
|
|
675
|
+
- Claude Code permission request handling preference
|
|
644
676
|
- translation provider settings and API key
|
|
645
677
|
- recent repository paths
|
|
646
678
|
|
|
679
|
+
Gateway state and audit logs:
|
|
680
|
+
|
|
681
|
+
```text
|
|
682
|
+
~/.vcm/gateway/settings.json
|
|
683
|
+
~/.vcm/gateway/audit.jsonl
|
|
684
|
+
```
|
|
685
|
+
|
|
686
|
+
Gateway credentials, iLink tokens, DM binding identity, cursors, context tokens,
|
|
687
|
+
and audit logs must stay outside connected repositories.
|
|
688
|
+
|
|
647
689
|
Repository-level VCM state:
|
|
648
690
|
|
|
649
691
|
```text
|
|
@@ -679,7 +721,7 @@ External Claude transcripts:
|
|
|
679
721
|
~/.claude/projects/<project-hash>/<claude-session-id>.jsonl
|
|
680
722
|
```
|
|
681
723
|
|
|
682
|
-
##
|
|
724
|
+
## 16. Packaging Expectations
|
|
683
725
|
|
|
684
726
|
Published npm packages must include built output:
|
|
685
727
|
|
|
@@ -698,7 +740,7 @@ npm run build && npm run verify:package
|
|
|
698
740
|
|
|
699
741
|
This protects against publishing raw TypeScript bin files or missing frontend assets.
|
|
700
742
|
|
|
701
|
-
##
|
|
743
|
+
## 17. Success Criteria
|
|
702
744
|
|
|
703
745
|
VCM V1 is successful when:
|
|
704
746
|
|
|
@@ -709,6 +751,7 @@ VCM V1 is successful when:
|
|
|
709
751
|
- Switching roles never loses the embedded terminal.
|
|
710
752
|
- Restart creates a fresh Claude session; Resume reconnects to the persisted one.
|
|
711
753
|
- Permission modes are reflected in the Claude command.
|
|
754
|
+
- Sidebar settings include Claude Code permission request handling with `off` and `allow all`; `off` is the default.
|
|
712
755
|
- Roles can route messages by writing fixed route files under `.ai/vcm/handoffs/messages/`.
|
|
713
756
|
- Manual orchestration lets the user inspect pending route-file messages without auto-submitting Enter.
|
|
714
757
|
- Auto orchestration can deliver pending route-file messages to idle running target roles.
|
|
@@ -716,6 +759,8 @@ VCM V1 is successful when:
|
|
|
716
759
|
- Round completion detection waits for the final role in a chained conversation and can alert with prompt plus sound.
|
|
717
760
|
- Translation settings save to `~/.vcm/settings.json`.
|
|
718
761
|
- Translation reads Claude transcript JSONL reliably after start, resume, and restart.
|
|
762
|
+
- Gateway can bind one Weixin DM identity to the desktop VCM instance, send
|
|
763
|
+
translated plain text to PM, and push translated PM replies back to Weixin.
|
|
719
764
|
- Terminal and translation panel have equal, stable reading space.
|
|
720
765
|
- Harness install/update preserves user content outside VCM managed blocks.
|
|
721
766
|
- Completed tasks can cleanly remove their worktree and VCM task metadata without affecting other tasks.
|
|
@@ -12,65 +12,80 @@ This document is an implementation plan, not a target-repository harness file.
|
|
|
12
12
|
|
|
13
13
|
## 1. Mobile Gateway Goal
|
|
14
14
|
|
|
15
|
-
VCM 0.2 should let
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
The gateway
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
-
|
|
24
|
-
-
|
|
25
|
-
-
|
|
26
|
-
|
|
27
|
-
|
|
15
|
+
VCM 0.2 should let one mobile Weixin DM bind to one desktop VCM instance and
|
|
16
|
+
talk to the current task's `project-manager` role.
|
|
17
|
+
|
|
18
|
+
The detailed gateway product design and implementation plan lives in
|
|
19
|
+
`docs/gateway-design.md`.
|
|
20
|
+
|
|
21
|
+
Current decisions:
|
|
22
|
+
|
|
23
|
+
- Support Weixin DM only. Group chat is not supported.
|
|
24
|
+
- Bind one mobile Weixin DM identity to one desktop VCM instance.
|
|
25
|
+
- Binding is not project-specific and not task-specific.
|
|
26
|
+
- The bound phone can select among the projects and tasks available to the
|
|
27
|
+
desktop VCM instance.
|
|
28
|
+
- Plain mobile text is sent only to the current task's `project-manager`.
|
|
29
|
+
- Gateway never sends directly to `architect`, `coder`, or `reviewer`.
|
|
30
|
+
- PM replies may be pushed to Weixin whenever gateway is enabled, even when the
|
|
31
|
+
PM turn was started from the desktop UI.
|
|
32
|
+
- When translation is enabled, mobile Chinese input is translated to English
|
|
33
|
+
before PM sees it, and PM English replies are translated to Chinese before
|
|
34
|
+
Weixin sees them.
|
|
35
|
+
- The prompt sent to PM does not include the original Chinese text.
|
|
36
|
+
- There is no multi-user allowlist. The security model is one bound DM identity.
|
|
28
37
|
|
|
29
38
|
## 2. Gateway Non-Goals
|
|
30
39
|
|
|
31
40
|
VCM 0.2 gateway should not:
|
|
32
41
|
|
|
33
|
-
- expose the
|
|
34
|
-
-
|
|
42
|
+
- expose the embedded terminal over Weixin
|
|
43
|
+
- support group chat
|
|
44
|
+
- support multiple mobile users
|
|
35
45
|
- store gateway credentials in the connected repository
|
|
36
|
-
-
|
|
46
|
+
- send directly to non-PM roles
|
|
47
|
+
- run shell commands from gateway messages
|
|
48
|
+
- provide approve/reject/start/stop workflow controls in the MVP
|
|
37
49
|
- bypass existing backend task/session/orchestration services
|
|
38
50
|
|
|
39
51
|
## 3. Gateway Shape
|
|
40
52
|
|
|
41
53
|
```text
|
|
42
54
|
Weixin mobile DM
|
|
43
|
-
-> Tencent iLink Bot API
|
|
55
|
+
-> Tencent iLink Bot API getupdates
|
|
44
56
|
-> VCM gateway channel: weixin-ilink
|
|
45
|
-
->
|
|
46
|
-
->
|
|
47
|
-
->
|
|
57
|
+
-> bound identity check
|
|
58
|
+
-> command parser or PM message bridge
|
|
59
|
+
-> existing VCM backend services
|
|
60
|
+
-> Tencent iLink sendmessage
|
|
48
61
|
```
|
|
49
62
|
|
|
50
|
-
The iLink channel
|
|
51
|
-
|
|
52
|
-
|
|
63
|
+
The iLink channel is outbound-first from the VCM process. It long-polls iLink
|
|
64
|
+
for direct messages and sends replies through the iLink send API. This avoids a
|
|
65
|
+
public callback URL for the first gateway version.
|
|
53
66
|
|
|
54
67
|
Suggested backend files:
|
|
55
68
|
|
|
56
69
|
```text
|
|
70
|
+
src/shared/types/gateway.ts
|
|
71
|
+
|
|
57
72
|
src/backend/gateway/
|
|
58
73
|
gateway-service.ts
|
|
59
74
|
gateway-settings-service.ts
|
|
60
75
|
gateway-command-parser.ts
|
|
61
|
-
gateway-
|
|
76
|
+
gateway-pm-bridge.ts
|
|
62
77
|
gateway-notifier.ts
|
|
78
|
+
gateway-audit-log.ts
|
|
63
79
|
channels/
|
|
64
80
|
weixin-ilink-channel.ts
|
|
65
81
|
|
|
66
82
|
src/backend/api/gateway-routes.ts
|
|
67
|
-
src/shared/types/gateway.ts
|
|
68
83
|
```
|
|
69
84
|
|
|
70
85
|
Gateway settings and secrets must live in app-local state:
|
|
71
86
|
|
|
72
87
|
```text
|
|
73
|
-
~/.vcm/settings.json
|
|
88
|
+
~/.vcm/gateway/settings.json
|
|
74
89
|
~/.vcm/gateway/audit.jsonl
|
|
75
90
|
```
|
|
76
91
|
|
|
@@ -79,62 +94,57 @@ log, `.ai/vcm/**`, or PR text.
|
|
|
79
94
|
|
|
80
95
|
## 4. Gateway Command Surface
|
|
81
96
|
|
|
82
|
-
|
|
97
|
+
MVP commands:
|
|
83
98
|
|
|
84
99
|
```text
|
|
100
|
+
/help
|
|
85
101
|
/status
|
|
102
|
+
/projects
|
|
103
|
+
/use-project <index-or-path>
|
|
86
104
|
/tasks
|
|
87
|
-
/task <
|
|
88
|
-
/
|
|
89
|
-
/
|
|
90
|
-
```
|
|
91
|
-
|
|
92
|
-
Initial state-changing commands:
|
|
93
|
-
|
|
94
|
-
```text
|
|
95
|
-
/pm <text>
|
|
96
|
-
/approve <id>
|
|
97
|
-
/reject <id>
|
|
98
|
-
/pause <task>
|
|
99
|
-
/resume <task>
|
|
105
|
+
/use-task <index-or-task-slug>
|
|
106
|
+
/translate on
|
|
107
|
+
/translate off
|
|
100
108
|
```
|
|
101
109
|
|
|
102
|
-
|
|
110
|
+
Plain text that does not start with `/` is treated as a PM message for the
|
|
111
|
+
current task.
|
|
103
112
|
|
|
104
|
-
|
|
105
|
-
- command parsing into a known action
|
|
106
|
-
- confirmation when destructive or high-risk
|
|
107
|
-
- audit logging with secrets redacted
|
|
113
|
+
## 5. Gateway Feasibility
|
|
108
114
|
|
|
109
|
-
The
|
|
110
|
-
|
|
111
|
-
|
|
115
|
+
The local reference implementation
|
|
116
|
+
`/Users/sheldon/Documents/New project 3/weixin-ilink-gateway-test` proves the
|
|
117
|
+
needed Tencent iLink primitives:
|
|
112
118
|
|
|
113
|
-
|
|
119
|
+
- QR login through `ilink/bot/get_bot_qrcode`.
|
|
120
|
+
- QR status polling through `ilink/bot/get_qrcode_status`.
|
|
121
|
+
- Long-poll receive through `ilink/bot/getupdates`.
|
|
122
|
+
- Text DM send through `ilink/bot/sendmessage`.
|
|
123
|
+
- Token, cursor, and context-token persistence outside repositories.
|
|
124
|
+
- Session-expiration handling through iLink error code `-14`.
|
|
114
125
|
|
|
115
|
-
|
|
126
|
+
VCM already has the other required primitives:
|
|
116
127
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
-
|
|
120
|
-
-
|
|
121
|
-
-
|
|
122
|
-
- rate-limit command handling
|
|
123
|
-
- redact secrets in logs
|
|
124
|
-
- confirmation-gate destructive actions
|
|
125
|
-
- never expose raw terminal write APIs to gateway commands
|
|
128
|
+
- controlled terminal submission through backend PTY services
|
|
129
|
+
- Claude Code `UserPromptSubmit` / `Stop` hooks
|
|
130
|
+
- Claude transcript parsing
|
|
131
|
+
- translation provider integration
|
|
132
|
+
- project/task/session services
|
|
126
133
|
|
|
127
134
|
## 6. Gateway Acceptance
|
|
128
135
|
|
|
129
|
-
Gateway is acceptable when:
|
|
130
|
-
|
|
131
|
-
-
|
|
132
|
-
-
|
|
133
|
-
- `/
|
|
134
|
-
-
|
|
135
|
-
-
|
|
136
|
-
-
|
|
137
|
-
-
|
|
136
|
+
Gateway MVP is acceptable when:
|
|
137
|
+
|
|
138
|
+
- desktop VCM can enable/disable gateway
|
|
139
|
+
- desktop VCM can QR-bind one Weixin DM identity
|
|
140
|
+
- the bound phone can DM `/status` and receive current VCM status
|
|
141
|
+
- the bound phone can list and select current project/task context
|
|
142
|
+
- the bound phone can send Chinese plain text to current task PM
|
|
143
|
+
- PM receives only the translated English prompt, without original Chinese
|
|
144
|
+
- gateway pushes PM assistant replies to Weixin whenever enabled
|
|
145
|
+
- PM replies are translated to Chinese when gateway translation is enabled
|
|
146
|
+
- restart does not replay already handled iLink messages or PM replies
|
|
147
|
+
- credentials and audit logs stay outside connected repos
|
|
138
148
|
|
|
139
149
|
## 7. Full Harness Goal
|
|
140
150
|
|
|
@@ -280,6 +280,11 @@ This skill is role-independent. It only handles long-running command execution,
|
|
|
280
280
|
bounded waiting, file-backed status, timeout, and log summaries. The caller
|
|
281
281
|
decides where to record command evidence.
|
|
282
282
|
|
|
283
|
+
VCM roles must not start background jobs. The only allowed background job is
|
|
284
|
+
`.ai/tools/run-long-check` when used through `vcm-long-running-validation`.
|
|
285
|
+
`vcm-long-running-validation` has a hard maximum timeout of 60 minutes; split
|
|
286
|
+
larger validation/build work or ask the user before suggesting anything longer.
|
|
287
|
+
|
|
283
288
|
## 10. Generated Context
|
|
284
289
|
|
|
285
290
|
The current example has two generated artifacts:
|
|
@@ -407,10 +412,24 @@ gateway-submitted prompts, and command authorization.
|
|
|
407
412
|
VCM 0.2 gateway should expose a conservative mobile command surface through
|
|
408
413
|
Tencent iLink Bot API / Weixin DM.
|
|
409
414
|
|
|
415
|
+
Gateway product rules:
|
|
416
|
+
|
|
417
|
+
- support Weixin DM only, not group chat
|
|
418
|
+
- bind one mobile Weixin DM identity to one desktop VCM instance
|
|
419
|
+
- do not bind gateway to a single project or task
|
|
420
|
+
- let the bound phone select among the projects and tasks available to that VCM
|
|
421
|
+
instance
|
|
422
|
+
- send ordinary mobile text only to the current task's `project-manager`
|
|
423
|
+
- do not send gateway messages directly to architect, coder, or reviewer
|
|
424
|
+
- when translation is enabled, send only translated English to PM and translated
|
|
425
|
+
Chinese back to Weixin
|
|
426
|
+
- do not include the original Chinese text in PM prompts
|
|
427
|
+
- use one bound DM identity; do not maintain a multi-user allowlist
|
|
428
|
+
|
|
410
429
|
Gateway settings and secrets live in app-local state:
|
|
411
430
|
|
|
412
431
|
```text
|
|
413
|
-
~/.vcm/settings.json
|
|
432
|
+
~/.vcm/gateway/settings.json
|
|
414
433
|
```
|
|
415
434
|
|
|
416
435
|
Gateway audit logs live outside connected repositories:
|
|
@@ -423,9 +442,9 @@ Rules:
|
|
|
423
442
|
|
|
424
443
|
- do not expose the full embedded terminal over Weixin
|
|
425
444
|
- do not store gateway credentials in connected repositories
|
|
426
|
-
-
|
|
427
|
-
-
|
|
428
|
-
- audit state
|
|
445
|
+
- reject or ignore messages outside the bound DM identity
|
|
446
|
+
- keep the MVP PM-only and avoid approve/reject/start/stop workflow commands
|
|
447
|
+
- audit gateway state changes and message handling with secrets redacted
|
|
429
448
|
- treat gateway authorization and command parsing as high-risk code
|
|
430
449
|
|
|
431
450
|
## 16. Minimum VCM Rules
|
package/package.json
CHANGED
|
@@ -11,6 +11,7 @@ const MANIFEST_PATH = ".ai/vcm-harness-manifest.json";
|
|
|
11
11
|
const HTML_BLOCK_PATTERN = /<!-- VCM:BEGIN(?:\s+version=\d+)? -->[\s\S]*?<!-- VCM:END -->/m;
|
|
12
12
|
const HASH_BLOCK_PATTERN = /# VCM:BEGIN(?:\s+version=\d+)?\n[\s\S]*?# VCM:END/m;
|
|
13
13
|
const VCM_HOOK_COMMAND = `sh -c 'if [ -z "\${VCM_TASK_SLUG:-}" ] || [ -z "\${VCM_ROLE:-}" ] || [ -z "\${VCM_API_URL:-}" ]; then exit 0; fi; node -e '"'"'let s="";process.stdin.setEncoding("utf8");process.stdin.on("data",d=>s+=d);process.stdin.on("end",()=>{let event={};try{event=s.trim()?JSON.parse(s):{};}catch{event={raw:s};}process.stdout.write(JSON.stringify({taskSlug:process.env.VCM_TASK_SLUG,role:process.env.VCM_ROLE,event}));});'"'"' | curl -fsS --max-time 2 -X POST "\${VCM_API_URL}/api/hooks/claude-code" -H "content-type: application/json" --data-binary @- >/dev/null || true'`;
|
|
14
|
+
const VCM_PERMISSION_REQUEST_HOOK_COMMAND = `sh -c 'if [ -z "\${VCM_TASK_SLUG:-}" ] || [ -z "\${VCM_ROLE:-}" ] || [ -z "\${VCM_API_URL:-}" ]; then exit 0; fi; node -e '"'"'let s="";process.stdin.setEncoding("utf8");process.stdin.on("data",d=>s+=d);process.stdin.on("end",()=>{let event={};try{event=s.trim()?JSON.parse(s):{};}catch{event={raw:s};}process.stdout.write(JSON.stringify({taskSlug:process.env.VCM_TASK_SLUG,role:process.env.VCM_ROLE,event}));});'"'"' | curl -fsS --max-time 5 -X POST "\${VCM_API_URL}/api/hooks/claude-code/permission-request" -H "content-type: application/json" --data-binary @- || true'`;
|
|
14
15
|
|
|
15
16
|
const AGENT_FRONTMATTER = {
|
|
16
17
|
"project-manager": {
|
|
@@ -39,7 +40,16 @@ const MANAGED_FILES = [
|
|
|
39
40
|
- Use the durable project docs below as role-relevant project truth.
|
|
40
41
|
- Read module-local \`CLAUDE.md\` before editing a subdirectory if one exists.
|
|
41
42
|
- Use \`vcm-route-message\` whenever a VCM role hands off work, asks another role a question, reports a result, reports a blocker, or raises a finding. Follow its write-then-stop rule.
|
|
42
|
-
- Use \`vcm-long-running-validation\` for
|
|
43
|
+
- Use \`vcm-long-running-validation\` for long-running validation. Follow the background job limits below.
|
|
44
|
+
|
|
45
|
+
## VCM Background Jobs
|
|
46
|
+
|
|
47
|
+
- Do not start background jobs.
|
|
48
|
+
- The only allowed background job is \`.ai/tools/run-long-check\` when used through the \`vcm-long-running-validation\` skill.
|
|
49
|
+
- \`vcm-long-running-validation\` has a hard maximum timeout of 60 minutes.
|
|
50
|
+
- Do not run or suggest operations expected to exceed 60 minutes without user approval.
|
|
51
|
+
- Design every single validation/build operation to complete within 60 minutes; split anything larger before running it.
|
|
52
|
+
- Do not end the current turn only to wait for a long-running shell callback.
|
|
43
53
|
|
|
44
54
|
## VCM Durable Project Docs
|
|
45
55
|
|
|
@@ -613,9 +623,11 @@ Use this skill for builds, browser checks, E2E tests, release suites, or any com
|
|
|
613
623
|
|
|
614
624
|
## Rule
|
|
615
625
|
|
|
616
|
-
Do not
|
|
626
|
+
Do not start background jobs.
|
|
617
627
|
|
|
618
|
-
|
|
628
|
+
The only allowed background job is \`.ai/tools/run-long-check\` when used through this skill.
|
|
629
|
+
|
|
630
|
+
This skill has a hard maximum timeout of 60 minutes. Do not run or suggest operations expected to exceed 60 minutes without user approval.
|
|
619
631
|
|
|
620
632
|
## Protocol
|
|
621
633
|
|
|
@@ -646,12 +658,15 @@ Example:
|
|
|
646
658
|
|
|
647
659
|
Timeout is not "unknown". It is a command result.
|
|
648
660
|
|
|
661
|
+
\`watch-job\` rejects timeouts over 60 minutes.
|
|
662
|
+
|
|
649
663
|
On timeout:
|
|
650
664
|
|
|
651
665
|
- summarize the latest log tail
|
|
652
666
|
- record the timeout in \`status.json\`
|
|
653
667
|
- report whether the timed-out process was stopped
|
|
654
668
|
- do not mark the command as passed
|
|
669
|
+
- do not continue the job in the background
|
|
655
670
|
|
|
656
671
|
\`watch-job\` should attempt to stop the timed-out command process group. If termination cannot be confirmed, say so in the summary.
|
|
657
672
|
|
|
@@ -916,11 +931,15 @@ import argparse
|
|
|
916
931
|
import json
|
|
917
932
|
import os
|
|
918
933
|
import signal
|
|
934
|
+
import sys
|
|
919
935
|
import time
|
|
920
936
|
from datetime import datetime, timezone
|
|
921
937
|
from pathlib import Path
|
|
922
938
|
|
|
923
939
|
|
|
940
|
+
MAX_TIMEOUT_SECONDS = 60 * 60
|
|
941
|
+
|
|
942
|
+
|
|
924
943
|
def root_dir() -> Path:
|
|
925
944
|
return Path(__file__).resolve().parents[2]
|
|
926
945
|
|
|
@@ -938,6 +957,16 @@ def parse_duration(value: str) -> float:
|
|
|
938
957
|
return float(value)
|
|
939
958
|
|
|
940
959
|
|
|
960
|
+
def validate_duration(name: str, value: float, maximum: float | None = None) -> bool:
|
|
961
|
+
if value <= 0:
|
|
962
|
+
print(f"error: {name} must be positive", file=sys.stderr)
|
|
963
|
+
return False
|
|
964
|
+
if maximum is not None and value > maximum:
|
|
965
|
+
print(f"error: {name} exceeds maximum 60m", file=sys.stderr)
|
|
966
|
+
return False
|
|
967
|
+
return True
|
|
968
|
+
|
|
969
|
+
|
|
941
970
|
def read_status(path: Path) -> dict:
|
|
942
971
|
return json.loads(path.read_text())
|
|
943
972
|
|
|
@@ -1056,8 +1085,16 @@ def main() -> int:
|
|
|
1056
1085
|
parser.add_argument("--interval", default="1s")
|
|
1057
1086
|
args = parser.parse_args()
|
|
1058
1087
|
|
|
1059
|
-
|
|
1060
|
-
|
|
1088
|
+
try:
|
|
1089
|
+
timeout = parse_duration(args.timeout)
|
|
1090
|
+
interval = parse_duration(args.interval)
|
|
1091
|
+
except ValueError as exc:
|
|
1092
|
+
print(f"error: invalid duration: {exc}", file=sys.stderr)
|
|
1093
|
+
return 2
|
|
1094
|
+
if not validate_duration("timeout", timeout, MAX_TIMEOUT_SECONDS):
|
|
1095
|
+
return 2
|
|
1096
|
+
if not validate_duration("interval", interval):
|
|
1097
|
+
return 2
|
|
1061
1098
|
directory = root_dir() / ".ai/vcm/jobs" / args.job_id
|
|
1062
1099
|
status_path = directory / "status.json"
|
|
1063
1100
|
|
|
@@ -1444,14 +1481,14 @@ function mergeVcmHooks(settings) {
|
|
|
1444
1481
|
}
|
|
1445
1482
|
}
|
|
1446
1483
|
|
|
1447
|
-
for (const eventName of ["UserPromptSubmit", "Stop"]) {
|
|
1484
|
+
for (const eventName of ["UserPromptSubmit", "Stop", "PermissionRequest"]) {
|
|
1448
1485
|
hooks[eventName] = [
|
|
1449
1486
|
...(Array.isArray(hooks[eventName]) ? hooks[eventName] : []),
|
|
1450
1487
|
{
|
|
1451
1488
|
hooks: [
|
|
1452
1489
|
{
|
|
1453
1490
|
type: "command",
|
|
1454
|
-
command: VCM_HOOK_COMMAND,
|
|
1491
|
+
command: eventName === "PermissionRequest" ? VCM_PERMISSION_REQUEST_HOOK_COMMAND : VCM_HOOK_COMMAND,
|
|
1455
1492
|
timeout: 5
|
|
1456
1493
|
}
|
|
1457
1494
|
]
|
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Copyright (c) 2014 The xterm.js authors. All rights reserved.
|
|
3
|
-
* Copyright (c) 2012-2013, Christopher Jeffrey (MIT License)
|
|
4
|
-
* https://github.com/chjj/term.js
|
|
5
|
-
* @license MIT
|
|
6
|
-
*
|
|
7
|
-
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
8
|
-
* of this software and associated documentation files (the "Software"), to deal
|
|
9
|
-
* in the Software without restriction, including without limitation the rights
|
|
10
|
-
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
11
|
-
* copies of the Software, and to permit persons to whom the Software is
|
|
12
|
-
* furnished to do so, subject to the following conditions:
|
|
13
|
-
*
|
|
14
|
-
* The above copyright notice and this permission notice shall be included in
|
|
15
|
-
* all copies or substantial portions of the Software.
|
|
16
|
-
*
|
|
17
|
-
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
18
|
-
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
19
|
-
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
20
|
-
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
21
|
-
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
22
|
-
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
23
|
-
* THE SOFTWARE.
|
|
24
|
-
*
|
|
25
|
-
* Originally forked from (with the author's permission):
|
|
26
|
-
* Fabrice Bellard's javascript vt100 for jslinux:
|
|
27
|
-
* http://bellard.org/jslinux/
|
|
28
|
-
* Copyright (c) 2011 Fabrice Bellard
|
|
29
|
-
* The original design remains. The terminal itself
|
|
30
|
-
* has been extended to include xterm CSI codes, among
|
|
31
|
-
* other features.
|
|
32
|
-
*/.xterm{cursor:text;position:relative;user-select:none;-ms-user-select:none;-webkit-user-select:none}.xterm.focus,.xterm:focus{outline:none}.xterm .xterm-helpers{position:absolute;top:0;z-index:5}.xterm .xterm-helper-textarea{padding:0;border:0;margin:0;position:absolute;opacity:0;left:-9999em;top:0;width:0;height:0;z-index:-5;white-space:nowrap;overflow:hidden;resize:none}.xterm .composition-view{background:#000;color:#fff;display:none;position:absolute;white-space:nowrap;z-index:1}.xterm .composition-view.active{display:block}.xterm .xterm-viewport{background-color:#000;overflow-y:scroll;cursor:default;position:absolute;right:0;left:0;top:0;bottom:0}.xterm .xterm-screen{position:relative}.xterm .xterm-screen canvas{position:absolute;left:0;top:0}.xterm .xterm-scroll-area{visibility:hidden}.xterm-char-measure-element{display:inline-block;visibility:hidden;position:absolute;top:0;left:-9999em;line-height:normal}.xterm.enable-mouse-events{cursor:default}.xterm.xterm-cursor-pointer,.xterm .xterm-cursor-pointer{cursor:pointer}.xterm.column-select.focus{cursor:crosshair}.xterm .xterm-accessibility:not(.debug),.xterm .xterm-message{position:absolute;left:0;top:0;bottom:0;right:0;z-index:10;color:transparent;pointer-events:none}.xterm .xterm-accessibility-tree:not(.debug) *::selection{color:transparent}.xterm .xterm-accessibility-tree{-webkit-user-select:text;user-select:text;white-space:pre}.xterm .live-region{position:absolute;left:-9999px;width:1px;height:1px;overflow:hidden}.xterm-dim{opacity:1!important}.xterm-underline-1{text-decoration:underline}.xterm-underline-2{text-decoration:double underline}.xterm-underline-3{text-decoration:wavy underline}.xterm-underline-4{text-decoration:dotted underline}.xterm-underline-5{text-decoration:dashed underline}.xterm-overline{text-decoration:overline}.xterm-overline.xterm-underline-1{text-decoration:overline underline}.xterm-overline.xterm-underline-2{text-decoration:overline double underline}.xterm-overline.xterm-underline-3{text-decoration:overline wavy underline}.xterm-overline.xterm-underline-4{text-decoration:overline dotted underline}.xterm-overline.xterm-underline-5{text-decoration:overline dashed underline}.xterm-strikethrough{text-decoration:line-through}.xterm-screen .xterm-decoration-container .xterm-decoration{z-index:6;position:absolute}.xterm-screen .xterm-decoration-container .xterm-decoration.xterm-decoration-top-layer{z-index:7}.xterm-decoration-overview-ruler{z-index:8;position:absolute;top:0;right:0;pointer-events:none}.xterm-decoration-top{z-index:2;position:relative}:root{color-scheme:light;font-family:Inter,ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,sans-serif;background:#f5f2ea;color:#1c2024;line-height:1.5}html,body,#root{height:100%}*{box-sizing:border-box}body{margin:0;min-width:320px;min-height:100vh;background:#f5f2ea}button,input,select,textarea{font:inherit}button{border:1px solid #9ba6ad;background:#f8f7f2;color:#1d252b;border-radius:6px;min-height:34px;padding:6px 10px;cursor:pointer}button:hover:not(:disabled){background:#eef4f2;border-color:#607d74}button:disabled{cursor:not-allowed;opacity:.55}.danger-button{border-color:#b84a45;background:#fff1ee;color:#8d211d;font-weight:750}.danger-button:hover:not(:disabled){border-color:#8d211d;background:#ffe2dc}input,select,textarea:not(.xterm-helper-textarea){width:100%;border:1px solid #b9b0a1;border-radius:6px;background:#fffdf8;color:#202326;padding:8px 10px}textarea:not(.xterm-helper-textarea){min-height:240px;resize:vertical;font-family:Menlo,Monaco,Consolas,monospace;font-size:12px}h1,h2,p{margin-top:0}h1{margin-bottom:4px;font-size:26px;line-height:1.15}h2{margin-bottom:10px;font-size:14px;letter-spacing:0}.app-shell{display:grid;grid-template-columns:minmax(280px,320px) minmax(0,1fr);height:100vh;min-height:0;overflow:hidden}.app-shell.is-sidebar-collapsed{grid-template-columns:46px minmax(0,1fr)}.app-sidebar{position:relative;display:flex;flex-direction:column;min-width:0;border-right:1px solid #d3c9b8;background:#fbfaf6;padding:14px;overflow:auto}.app-shell.is-sidebar-collapsed .app-sidebar{overflow:hidden;padding:8px}.sidebar-toggle{position:absolute;top:10px;right:10px;z-index:2;display:grid;place-items:center;width:28px;min-height:28px;padding:0;background:#fffdf8}.sidebar-toggle:before{width:8px;height:8px;border-color:currentColor;border-style:solid;border-width:0 2px 2px 0;content:"";transform:translate(2px) rotate(135deg)}.app-shell.is-sidebar-collapsed .sidebar-toggle{left:9px;right:auto}.app-shell.is-sidebar-collapsed .sidebar-toggle:before{transform:translate(-2px) rotate(-45deg)}.sidebar-content{display:flex;flex:1;min-width:0;min-height:0}.project-dashboard{display:flex;flex:1;flex-direction:column;width:100%;min-height:100%}.app-shell.is-sidebar-collapsed .sidebar-content{width:0;opacity:0;pointer-events:none}.app-main{min-width:0;height:100%;padding:14px 16px;overflow:auto}.brand-header{display:flex;gap:12px;align-items:baseline;margin-bottom:10px;padding-right:34px}.brand-header strong{font-size:18px}.brand-header span,.muted{color:#667071;font-size:13px}.sidebar-section{margin-bottom:8px;border:1px solid #e0d6c7;border-radius:8px;background:#fffdfa;overflow:hidden}.sidebar-section-toggle{display:grid;grid-template-columns:minmax(0,1fr) auto;gap:8px;align-items:center;width:100%;min-height:34px;border:0;border-radius:0;background:transparent;color:#1c2024;font-size:13px;font-weight:750;text-align:left}.sidebar-section-toggle:hover{background:#f5f1e8}.sidebar-section-toggle[aria-expanded=true]{border-bottom:1px solid #ece5d9}.sidebar-section-chevron{width:8px;height:8px;border-color:currentColor;border-style:solid;border-width:0 2px 2px 0;transform:rotate(45deg)}.sidebar-section-toggle[aria-expanded=true] .sidebar-section-chevron{transform:rotate(-135deg)}.sidebar-section-content{padding:8px}.repo-connect,.project-summary,.harness-panel,.task-create{margin:0}.inline-form{display:grid;grid-template-columns:minmax(0,1fr);gap:8px}.inline-form.has-recent-paths{grid-template-columns:minmax(0,1fr) auto}.inline-form>input{grid-column:1 / -1}.inline-form>button{justify-self:end}.inline-form.has-recent-paths>button{justify-self:auto}.repo-recent-select{min-width:0;max-width:none}.project-summary dl{display:grid;gap:8px;margin:0}.project-summary div{min-width:0}.project-summary dt{color:#6c6255;font-size:12px}.project-summary dd{margin:0;overflow-wrap:anywhere;font-size:13px}.warnings,.error-banner{border:1px solid #c87b54;background:#fff4ed;color:#6f3218;border-radius:6px;padding:10px 12px}.flow-pause-alert-backdrop{position:fixed;top:0;right:0;bottom:0;left:0;z-index:100;display:grid;place-items:center;background:#1f242b85;padding:20px}.flow-pause-alert{display:grid;gap:12px;width:min(460px,100%);border:1px solid #2f6f73;border-radius:8px;background:#e8f4f2;color:#123f43;box-shadow:0 28px 72px #1f242b47;padding:22px}.flow-pause-alert-kicker{margin:0;color:#2f6f73;font-size:12px;font-weight:700;text-transform:uppercase}.flow-pause-alert h2{margin:0;font-size:24px;line-height:1.15}.flow-pause-alert p{margin:0;font-size:14px;line-height:1.5}.flow-pause-alert-hint{color:#49666a}.flow-pause-alert button{justify-self:end;min-width:110px;border:1px solid #2f6f73;background:#2f6f73;color:#fffdf8}.flow-pause-alert button:hover{background:#245c60}.warnings{margin:12px 0 0;padding-left:26px;font-size:13px}.harness-panel{display:grid;gap:10px}.harness-stage{display:grid;gap:8px}.harness-panel-header{display:flex;justify-content:space-between;gap:8px;align-items:center}.harness-panel-header h2,.harness-panel-header p,.harness-result p{margin-bottom:0}.harness-actions{display:flex;flex-wrap:wrap;gap:6px;justify-content:flex-end}.harness-file-list{display:grid;gap:6px;margin:0;padding:0;list-style:none}.harness-file-list li{display:grid;grid-template-columns:minmax(0,1fr) auto;gap:8px;align-items:center;border:1px solid #ece5d9;border-radius:6px;padding:6px 8px;background:#fffdfa}.harness-file-list span{overflow:hidden;font-size:12px;font-weight:650;text-overflow:ellipsis;white-space:nowrap}.harness-changes,.harness-result{border:1px solid #e0d6c7;border-radius:6px;padding:8px;background:#f8f7f2;font-size:12px}.harness-changes h3{margin:0 0 4px;font-size:12px}.harness-changes ul,.harness-result ul{margin:0;padding-left:18px}.harness-bootstrap-session{display:grid;gap:8px;border:1px solid #e0d6c7;border-radius:6px;padding:8px;background:#f8f7f2;font-size:12px}.harness-bootstrap-session>div:first-child{display:grid;grid-template-columns:minmax(0,1fr) auto;gap:8px;align-items:center}.harness-bootstrap-session span:first-child{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.harness-bootstrap-modal{position:fixed;top:0;right:0;bottom:0;left:0;z-index:80;display:grid;grid-template-columns:minmax(0,1fr);grid-template-rows:minmax(0,1fr);background:#1f242b94;padding:14px}.harness-bootstrap-modal-surface{min-width:0;min-height:0;display:grid;grid-template-rows:auto minmax(0,1fr);gap:10px;border:1px solid #b9b0a1;border-radius:8px;background:#fffdf8;box-shadow:0 22px 64px #1f242b47;padding:10px}.harness-bootstrap-modal-header{display:flex;gap:12px;align-items:center;justify-content:space-between;min-width:0}.harness-bootstrap-modal-header p{max-width:min(720px,70vw);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.harness-bootstrap-modal-actions{display:flex;flex-wrap:wrap;gap:8px;align-items:center;justify-content:flex-end}.harness-bootstrap-terminal{min-width:0;min-height:0;display:grid}.task-create form{display:grid;gap:8px}.task-create-preview{display:grid;gap:2px;border:1px solid #e0d6c7;border-radius:6px;background:#f8f7f2;padding:6px 8px}.task-create-option{display:flex;align-items:center;gap:8px;color:#1f242b;font-size:13px;font-weight:700}.task-create-option input{width:16px;height:16px;margin:0}.task-create-preview span{color:#255f3d;font-size:12px;font-weight:700}.task-create-preview small{overflow:hidden;color:#687273;font-size:11px;text-overflow:ellipsis;white-space:nowrap}.task-nav{display:grid;gap:8px}.task-nav-item{display:grid;grid-template-columns:minmax(0,1fr) auto;gap:8px;align-items:center;text-align:left}.task-nav-item.is-active,.role-tab.is-active{border-color:#2f6f73;background:#e8f1ef}.sidebar-settings{display:grid;gap:8px}.sidebar-settings button{display:grid;grid-template-columns:minmax(0,1fr) auto;gap:8px;align-items:center;width:100%;text-align:left}.sidebar-settings .settings-toggle.is-active{border-color:#2f6f73;background:#e8f1ef}.sidebar-settings .theme-mode-toggle span:last-child{font-weight:750}.task-status-dock{display:grid;gap:10px;margin-top:auto;border-top:1px solid #d3c9b8;padding-top:10px}.task-status-dock-title,.current-round-title{display:grid;grid-template-columns:minmax(0,1fr) auto;gap:8px;align-items:center}.task-status-dock-title strong{overflow:hidden;font-size:13px;line-height:1.25;text-overflow:ellipsis;white-space:nowrap}.task-status-stats{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:6px;margin:0}.task-status-stats div,.current-round-status{min-width:0;border:1px solid #e0d6c7;border-radius:6px;background:#fffdfa}.task-status-stats div{padding:6px 7px}.task-status-stats dt{color:#6c6255;font-size:10px;font-weight:700;text-transform:uppercase}.task-status-stats dd{margin:0;overflow:hidden;color:#1f242b;font-size:12px;font-weight:750;text-overflow:ellipsis;white-space:nowrap}.current-round-status{display:grid;gap:8px;padding:8px}.current-round-title span:first-child{color:#3a4647;font-size:12px;font-weight:750}.workspace-header{display:grid;grid-template-columns:minmax(120px,max-content) minmax(420px,1fr) auto;gap:10px;align-items:center;margin-bottom:6px}.workspace-title-line{display:flex;min-width:0}.workspace-title-line h1{margin-bottom:0;font-size:18px;line-height:1.15}.workspace-header-actions{display:flex;gap:8px;align-items:center;justify-content:flex-end;min-width:max-content}.role-tabs{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:6px;margin-bottom:8px;min-width:0}.workspace-header .role-tabs{margin-bottom:0}.role-tab{display:grid;grid-template-columns:minmax(0,1fr) auto;gap:6px;align-items:center;min-height:30px;padding:4px 8px;text-align:left}.workspace-grid{display:grid;grid-template-columns:minmax(0,1fr);flex:1;gap:10px;align-items:stretch;min-height:0}.workspace-main{min-width:0;min-height:0;display:flex;flex-direction:column;gap:8px}.role-console-stack{min-width:0;min-height:0;flex:1;display:flex;flex-direction:column}.role-console-panel{min-width:0;min-height:0;flex:1;display:none}.role-console-panel.is-active{display:flex;flex-direction:column}.session-console,.message-panel,.event-log,.empty-workspace{border:1px solid #d6d0c6;border-radius:8px;background:#fffdf8;padding:10px}.task-workspace{display:flex;flex-direction:column;gap:8px;height:100%;min-height:0}.session-console{display:grid;grid-template-rows:auto minmax(0,1fr);gap:8px;flex:1;height:100%;min-height:0}.session-console-top{display:flex;gap:10px;align-items:center;justify-content:space-between}.session-console-actions{display:flex;flex-wrap:wrap;gap:8px;align-items:center;justify-content:flex-end}.session-controls{display:flex;flex-wrap:wrap;gap:8px;align-items:center;justify-content:space-between;margin:0 0 8px}.permission-mode-field{display:grid;grid-template-columns:auto minmax(180px,260px);gap:8px;align-items:center;width:fit-content;max-width:100%}.permission-mode-field span{color:#5f6a6c;font-size:13px;font-weight:650}.permission-mode-field small{display:block;color:#7b8587;font-size:11px;font-weight:500;line-height:1.2}.permission-mode-field select{width:100%;min-height:30px;border:1px solid #b9b0a1;border-radius:6px;background:#fffdf8;color:#202326;padding:4px 8px}.session-toolbar{display:flex;flex-wrap:wrap;gap:6px;justify-content:flex-end}.session-toolbar button{min-height:30px;padding:4px 9px}.translation-toggle{display:inline-flex;align-items:center;justify-content:center;min-height:28px;border:1px solid #b5bec4;border-radius:6px;background:#fffefa;color:#4f5558;font-size:12px;font-weight:650;padding:3px 10px;white-space:nowrap}.translation-toggle.is-active{border-color:#2f7e84;background:#e8f4f2;color:#145e64}.translation-settings-grid input[type=checkbox]{width:auto}.session-console-body{display:grid;grid-template-columns:minmax(0,1fr);min-width:0;min-height:0;height:100%}.session-console-body.has-translation{display:grid;grid-template-columns:minmax(0,1fr) minmax(0,1fr);gap:10px;align-items:stretch}.terminal-pane,.translation-pane{display:grid;min-width:0;min-height:0;height:100%}.terminal-frame,.terminal-empty{width:100%;height:100%;min-height:0;border-radius:6px;overflow:hidden;background:#111316}.terminal-empty{display:grid;place-items:center;align-content:center;gap:8px;color:#d6d0c6;border:1px solid #292d31}.translation-panel{display:grid;grid-template-rows:auto minmax(0,1fr) auto;gap:8px;height:100%;min-height:0;border:1px solid #292d31;border-radius:6px;background:#0d1117;color:#d6deeb;padding:8px;min-width:0;width:100%;overflow:hidden;font-family:Menlo,Monaco,Consolas,monospace}.translation-panel-header{display:grid;gap:3px}.translation-panel-titlebar,.translation-panel-actions,.translation-status-row{display:flex;flex-wrap:wrap;gap:6px;align-items:center;justify-content:space-between}.translation-panel-header h2,.translation-panel-header p{margin-bottom:0}.translation-panel-titlebar h2{font-size:16px}.translation-panel-header p,.translation-composer span{color:#8b949e;font-size:12px}.translation-panel-actions button{border-color:#3a4149;background:#161b22;color:#d6deeb;min-height:26px;padding:2px 8px;font-size:12px}.translation-panel-actions button:hover:not(:disabled),.translation-composer-actions button:hover:not(:disabled){border-color:#58a6ff;background:#1f2937}.translation-panel-actions .auto-send-toggle.is-active{border-color:#56d364;background:#12261a;color:#d6deeb}.translation-panel-actions{justify-content:flex-end}.translation-status-row{flex-wrap:nowrap}.translation-status-row p:last-child{flex:0 0 auto;text-align:right}.translation-entry-list{display:grid;align-content:start;gap:8px;min-height:0;min-width:0;overflow-x:hidden;overflow-y:auto;scrollbar-color:#4b5563 #0d1117}.translation-entry{border:0;border-radius:0;background:transparent;min-width:0;max-width:100%;padding:0}.translation-conversation-boundary{display:grid;grid-template-columns:minmax(18px,1fr) auto auto auto minmax(18px,1fr);gap:8px;align-items:center;color:#8b949e;font-family:Menlo,Monaco,Consolas,monospace;font-size:12px;line-height:1.4;white-space:nowrap}.translation-boundary-dash{min-width:0;border-top:1px dashed #3a4149}.translation-entry.is-user-input{border-top:4px solid #3a4149;margin-top:14px;padding-top:14px}.translation-entry.is-user-input:first-child{margin-top:0}.translation-entry pre{box-sizing:border-box;margin:0;max-height:none;max-width:100%;min-width:0;overflow:visible;white-space:pre-wrap;overflow-wrap:anywhere;font-family:Menlo,Monaco,Consolas,monospace;font-size:12px;line-height:1.45;color:#d6deeb}.translation-markdown{max-width:100%;min-width:0;color:#d6deeb;font-size:12px;line-height:1.55;overflow-wrap:anywhere}.translation-markdown>:first-child{margin-top:0}.translation-markdown>:last-child{margin-bottom:0}.translation-markdown p,.translation-markdown ul,.translation-markdown ol,.translation-markdown blockquote,.translation-markdown pre,.translation-markdown table{margin:0 0 8px}.translation-markdown h1,.translation-markdown h2,.translation-markdown h3,.translation-markdown h4,.translation-markdown h5,.translation-markdown h6{margin:10px 0 6px;color:#f0f6fc;font-weight:700;line-height:1.25}.translation-markdown h1{font-size:17px}.translation-markdown h2{font-size:15px}.translation-markdown h3,.translation-markdown h4,.translation-markdown h5,.translation-markdown h6{font-size:13px}.translation-markdown ul,.translation-markdown ol{padding-left:22px}.translation-markdown li{margin:2px 0}.translation-markdown .task-list-item{list-style:none}.translation-markdown input[type=checkbox]{width:13px;height:13px;margin:0 6px 0 0;accent-color:#56d364}.translation-markdown blockquote{border-left:3px solid #3a4149;color:#b7c0ca;padding-left:10px}.translation-markdown a{color:#79c0ff}.translation-markdown code{border-radius:4px;background:#161b22;color:#f0f6fc;padding:1px 4px;font-family:Menlo,Monaco,Consolas,monospace;font-size:.95em}.translation-markdown pre{overflow-x:auto;border:1px solid #292d31;border-radius:6px;background:#111316;padding:8px;white-space:pre}.translation-markdown pre code{background:transparent;padding:0}.translation-markdown table{display:block;max-width:100%;overflow-x:auto;border-collapse:collapse}.translation-markdown img{max-width:100%;border-radius:4px}.translation-markdown th,.translation-markdown td{border:1px solid #30363d;padding:4px 6px;text-align:left;vertical-align:top}.translation-markdown hr{border:0;border-top:1px solid #292d31;margin:10px 0}.translation-entry.is-tool-output pre{display:block;overflow:hidden;color:#7d8590;text-overflow:ellipsis;white-space:nowrap;width:100%}.translation-entry-note{margin:2px 0 0;color:#6e7681;font-size:10px;line-height:1.35}.translation-entry-note.is-error{color:#a3715f}.translation-composer{display:grid;gap:6px;border-top:1px solid #292d31;padding-top:8px}.translation-composer-row{display:grid;grid-template-columns:minmax(0,1fr) auto;gap:8px;align-items:stretch}.translation-composer textarea{width:100%;min-height:38px;max-height:88px;border-color:#3a4149;background:#0d1117;color:#d6deeb;font-family:inherit;font-size:12px;line-height:1.35;resize:vertical}.translation-composer textarea::placeholder{color:#7d8590}.translation-composer textarea::selection{background:#8b949e59;color:#fff}.translation-composer textarea:focus,.translation-composer textarea:focus-visible{border-color:#4b5563;outline:none;box-shadow:none}.translation-composer-actions{display:grid;align-content:start;gap:6px;min-width:104px}.translation-composer-actions button{border-color:#3a4149;background:#161b22;color:#d6deeb;width:100%;min-height:38px;padding:4px 9px;font-size:12px}.translation-panel .muted{color:#8b949e}.translation-panel .error-banner{border-color:#da7b72;background:#2d1518;color:#ffdcd7}.modal-backdrop{position:fixed;top:0;right:0;bottom:0;left:0;z-index:20;display:grid;place-items:center;background:#181c1f61;padding:18px}.translation-settings-modal{display:grid;gap:12px;width:min(980px,100%);max-height:min(760px,92vh);overflow:auto;border:1px solid #d6d0c6;border-radius:8px;background:#fffdf8;padding:14px}.message-modal,.event-modal{display:grid;grid-template-rows:auto minmax(0,1fr);gap:12px;width:min(980px,100%);max-height:min(760px,92vh);overflow:hidden;border:1px solid #d6d0c6;border-radius:8px;background:#fffdf8;padding:14px}.translation-settings-modal header,.message-modal header,.event-modal header,.translation-settings-modal footer{display:flex;gap:8px;align-items:center;justify-content:space-between}.translation-prompt-settings{display:grid;gap:10px;border-top:1px solid #ece5d9;padding-top:12px}.translation-prompt-settings header{display:flex;gap:10px;align-items:end;justify-content:space-between}.translation-prompt-settings h3,.translation-prompt-settings p{margin-bottom:0}.translation-prompt-settings h3{font-size:13px}.translation-prompt-settings textarea{min-height:120px;max-height:260px;font-family:Menlo,Monaco,Consolas,monospace;font-size:12px}.translation-prompt-stack{display:grid;gap:10px}.translation-prompt-stack label{display:grid;gap:4px}.translation-prompt-stack span{color:#4f5558;font-size:12px;font-weight:650}.translation-settings-modal h2,.message-modal h2,.message-modal p,.event-modal h2,.event-modal p,.translation-settings-modal p{margin-bottom:0}.translation-settings-grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:10px}.translation-settings-grid label{display:grid;gap:4px}.translation-settings-grid span{color:#4f5558;font-size:12px;font-weight:650}.translation-settings-grid select,.translation-prompt-settings select{min-height:34px;border:1px solid #b9b0a1;border-radius:6px;background:#fffdf8;color:#202326;padding:6px 10px}.translation-test-result{border-radius:6px;padding:8px;font-size:13px}.translation-test-result.is-ok{border:1px solid #6ea77e;background:#e6f3e9;color:#245334}.translation-test-result.is-error{border:1px solid #c46e5f;background:#fae9e6;color:#6f2b21}.message-panel{display:grid;gap:8px}.message-modal .message-panel,.event-modal .event-log{min-height:0;overflow:auto;border:0;background:transparent;padding:0}.message-panel-header{display:flex;justify-content:space-between;gap:12px;align-items:center}.message-panel-header h2,.message-panel-header p{margin-bottom:0}.message-controls,.message-mode-toggle,.modal-actions,.message-actions{display:flex;flex-wrap:wrap;gap:8px;align-items:center}.message-mode-toggle{color:#4f5558;font-size:13px;font-weight:650}.message-mode-toggle input{width:auto}.message-list{display:grid;gap:8px;margin:0;padding:0;list-style:none}.message-item{display:grid;grid-template-columns:minmax(0,1fr) auto;gap:10px;align-items:start;border:1px solid #ece5d9;border-radius:6px;padding:8px;background:#fffdfa}.message-meta{display:flex;flex-wrap:wrap;gap:8px;align-items:center;margin-bottom:4px}.message-meta time,.message-meta span:not(.status-badge),.message-reason,.message-path{color:#667071;font-size:12px}.message-sequence{min-width:32px;color:#1f242b;font-weight:800}.message-actions button{min-height:30px;padding:4px 10px}.message-item p{display:-webkit-box;margin-bottom:4px;overflow:hidden;-webkit-box-orient:vertical;-webkit-line-clamp:2}.message-reason,.message-path{display:block;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.event-log ol{margin:0;padding-left:22px;font-size:13px}.event-log{max-height:92px;overflow:auto}.event-log h2{margin-bottom:4px;font-size:13px}.event-log p{margin-bottom:0}.event-log li{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.status-badge{display:inline-flex;align-items:center;justify-content:center;min-width:66px;min-height:20px;border-radius:999px;padding:2px 8px;border:1px solid #c7c1b8;background:#f2eee7;color:#4f5558;font-size:11px;white-space:nowrap}.status-active,.status-running,.status-ok{border-color:#6ea77e;background:#e6f3e9;color:#245334}.status-created,.status-idle{border-color:#9aa3a8;background:#edf0f1;color:#465056}.status-blocked,.status-crashed,.status-missing,.status-empty{border-color:#c46e5f;background:#fae9e6;color:#6f2b21}.status-planning,.status-settling,.status-waiting,.status-starting,.status-incomplete{border-color:#c4a34e;background:#f7efcf;color:#604e16}.status-exited,.status-done,.status-stopped,.status-resumable{border-color:#7b98b8;background:#e9f0f8;color:#2e4e70}.status-queued,.status-translating,.status-ready,.status-create,.status-insert,.status-update{border-color:#c4a34e;background:#f7efcf;color:#604e16}.status-rejected,.status-failed,.status-delivery_failed,.status-response_ready_missing_result,.status-cancelled,.status-blocked{border-color:#c46e5f;background:#fae9e6;color:#6f2b21}.status-pending{border-color:#c7c1b8;background:#f2eee7;color:#4f5558}.status-translated,.status-preserved{border-color:#7b98b8;background:#e9f0f8;color:#2e4e70}.empty-workspace{max-width:680px}@media(max-width:980px){.app-shell,.workspace-grid{grid-template-columns:1fr}.app-sidebar{border-right:0;border-bottom:1px solid #d3c9b8}.role-tabs{grid-template-columns:repeat(2,minmax(0,1fr))}.session-console-body.has-translation,.translation-settings-grid{grid-template-columns:1fr}}@media(max-width:560px){.app-main,.app-sidebar{padding:12px}.workspace-header,.inline-form,.inline-form.has-recent-paths{grid-template-columns:1fr;display:grid}.role-tabs{grid-template-columns:1fr}.terminal-frame,.terminal-empty{min-height:300px;height:54vh}.permission-mode-field{grid-template-columns:1fr;width:100%}}.harness-bootstrap-terminal .terminal-frame{height:100%;min-height:0}:root[data-theme=dark]{color-scheme:dark;background:#0d1117;color:#e6edf3}:root[data-theme=dark] body,:root[data-theme=dark] .app-main{background:#0d1117;color:#e6edf3}:root[data-theme=dark] button{border-color:#3d4652;background:#161b22;color:#e6edf3}:root[data-theme=dark] button:hover:not(:disabled){border-color:#58a6ff;background:#1f2937}:root[data-theme=dark] input,:root[data-theme=dark] select,:root[data-theme=dark] textarea:not(.xterm-helper-textarea){border-color:#3d4652;background:#0d1117;color:#e6edf3}:root[data-theme=dark] input::placeholder,:root[data-theme=dark] textarea::placeholder{color:#7d8590}:root[data-theme=dark] .danger-button{border-color:#da7b72;background:#2d1518;color:#ffdcd7}:root[data-theme=dark] .danger-button:hover:not(:disabled){border-color:#ffa198;background:#3d1f23}:root[data-theme=dark] .app-sidebar{border-color:#30363d;background:#0f141b}:root[data-theme=dark] .sidebar-toggle{background:#161b22}:root[data-theme=dark] .brand-header span,:root[data-theme=dark] .muted,:root[data-theme=dark] .message-meta time,:root[data-theme=dark] .message-meta span:not(.status-badge),:root[data-theme=dark] .message-reason,:root[data-theme=dark] .message-path{color:#8b949e}:root[data-theme=dark] .sidebar-section,:root[data-theme=dark] .session-console,:root[data-theme=dark] .message-panel,:root[data-theme=dark] .event-log,:root[data-theme=dark] .empty-workspace,:root[data-theme=dark] .translation-settings-modal,:root[data-theme=dark] .message-modal,:root[data-theme=dark] .event-modal{border-color:#30363d;background:#11161d}:root[data-theme=dark] .sidebar-section-toggle{color:#e6edf3}:root[data-theme=dark] .sidebar-section-toggle:hover{background:#161b22}:root[data-theme=dark] .sidebar-section-toggle[aria-expanded=true],:root[data-theme=dark] .translation-prompt-settings{border-color:#30363d}:root[data-theme=dark] .project-summary dt,:root[data-theme=dark] .permission-mode-field span,:root[data-theme=dark] .translation-settings-grid span,:root[data-theme=dark] .translation-prompt-stack span,:root[data-theme=dark] .message-mode-toggle{color:#b7c0ca}:root[data-theme=dark] .permission-mode-field small,:root[data-theme=dark] .task-create-preview small{color:#8b949e}:root[data-theme=dark] .warnings,:root[data-theme=dark] .error-banner{border-color:#da7b72;background:#2d1518;color:#ffdcd7}:root[data-theme=dark] .flow-pause-alert-backdrop{background:#010409b8}:root[data-theme=dark] .flow-pause-alert{border-color:#56d4dd;background:#10262b;color:#d6fbff;box-shadow:0 28px 72px #0104099e}:root[data-theme=dark] .flow-pause-alert-kicker,:root[data-theme=dark] .flow-pause-alert-hint{color:#9debf2}:root[data-theme=dark] .flow-pause-alert button{border-color:#56d4dd;background:#56d4dd;color:#061114}:root[data-theme=dark] .flow-pause-alert button:hover{background:#84f3fa}:root[data-theme=dark] .harness-file-list li,:root[data-theme=dark] .harness-changes,:root[data-theme=dark] .harness-result,:root[data-theme=dark] .harness-bootstrap-session,:root[data-theme=dark] .task-create-preview,:root[data-theme=dark] .task-status-stats div,:root[data-theme=dark] .current-round-status,:root[data-theme=dark] .message-item{border-color:#30363d;background:#0d1117}:root[data-theme=dark] .harness-bootstrap-modal{background:#010409b8}:root[data-theme=dark] .harness-bootstrap-modal-surface{border-color:#30363d;background:#0d1117;box-shadow:0 22px 64px #0104099e}:root[data-theme=dark] .task-create-option{color:#e6edf3}:root[data-theme=dark] .task-status-dock{border-color:#30363d}:root[data-theme=dark] .task-create-preview span{color:#7ee787}:root[data-theme=dark] .task-status-stats dt{color:#8b949e}:root[data-theme=dark] .task-status-stats dd,:root[data-theme=dark] .current-round-title span:first-child{color:#e6edf3}:root[data-theme=dark] .message-sequence{color:#f0f6fc}:root[data-theme=dark] .task-nav-item.is-active,:root[data-theme=dark] .role-tab.is-active,:root[data-theme=dark] .sidebar-settings .settings-toggle.is-active,:root[data-theme=dark] .translation-toggle.is-active{border-color:#56d4dd;background:#10262b;color:#d6fbff}:root[data-theme=dark] .translation-toggle{border-color:#3d4652;background:#161b22;color:#b7c0ca}:root[data-theme=dark] .permission-mode-field select,:root[data-theme=dark] .translation-settings-grid select,:root[data-theme=dark] .translation-prompt-settings select{border-color:#3d4652;background:#0d1117;color:#e6edf3}:root[data-theme=dark] .terminal-empty{border-color:#30363d}:root[data-theme=dark] .modal-backdrop{background:#010409b8}:root[data-theme=dark] .message-modal .message-panel,:root[data-theme=dark] .event-modal .event-log{background:transparent}:root[data-theme=dark] .status-badge{border-color:#3d4652;background:#161b22;color:#d6deeb}:root[data-theme=dark] .translation-test-result.is-ok,:root[data-theme=dark] .status-active,:root[data-theme=dark] .status-running,:root[data-theme=dark] .status-ok{border-color:#3fb950;background:#12261a;color:#aff5b4}:root[data-theme=dark] .status-created,:root[data-theme=dark] .status-idle{border-color:#69717d;background:#20262f;color:#d6deeb}:root[data-theme=dark] .translation-test-result.is-error,:root[data-theme=dark] .status-blocked,:root[data-theme=dark] .status-crashed,:root[data-theme=dark] .status-missing,:root[data-theme=dark] .status-empty,:root[data-theme=dark] .status-rejected,:root[data-theme=dark] .status-failed,:root[data-theme=dark] .status-delivery_failed,:root[data-theme=dark] .status-response_ready_missing_result,:root[data-theme=dark] .status-cancelled{border-color:#f85149;background:#2d1518;color:#ffdcd7}:root[data-theme=dark] .status-planning,:root[data-theme=dark] .status-settling,:root[data-theme=dark] .status-waiting,:root[data-theme=dark] .status-starting,:root[data-theme=dark] .status-incomplete,:root[data-theme=dark] .status-queued,:root[data-theme=dark] .status-translating,:root[data-theme=dark] .status-ready,:root[data-theme=dark] .status-create,:root[data-theme=dark] .status-insert,:root[data-theme=dark] .status-update{border-color:#d29922;background:#2d2208;color:#f8e3a1}:root[data-theme=dark] .status-exited,:root[data-theme=dark] .status-done,:root[data-theme=dark] .status-stopped,:root[data-theme=dark] .status-resumable,:root[data-theme=dark] .status-translated,:root[data-theme=dark] .status-preserved{border-color:#388bfd;background:#10223a;color:#c9e2ff}:root[data-theme=dark] .status-pending{border-color:#3d4652;background:#161b22;color:#d6deeb}
|