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.
@@ -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 10 minutes, the chime plays 3 times, 1.4 seconds apart, and stops. If the flow lasted 10 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.
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 normal pause transition is timer-driven from the `Stop` event. Round-state reads also settle expired deadlines as a recovery fallback after restarts, sleep, or missed timers.
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 `pauseId`. Flow duration is measured from the first `UserPromptSubmit` to the last `Stop`; the 10 second settle window is not counted. Pauses under 10 minutes trigger the weak 3-chime reminder at 1.4 second intervals. Pauses at or above 10 minutes trigger the strong alert dialog and repeating sound until confirmation.
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 `Stop` hooks into `.claude/settings.json`.
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. Local State
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
- ## 15. Packaging Expectations
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
- ## 16. Success Criteria
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 the user monitor and lightly control VCM from mobile Weixin
16
- direct messages.
17
-
18
- The gateway is a mobile command surface, not a remote terminal. It should help
19
- the user:
20
-
21
- - check current task status
22
- - see pending role messages or blockers
23
- - approve or reject confirmation-gated actions
24
- - send a short PM prompt
25
- - receive high-value notifications
26
-
27
- The first channel is Tencent iLink Bot API / Weixin DM.
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 full embedded terminal over Weixin
34
- - promise ordinary Weixin group chat support
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
- - let unauthenticated Weixin users control VCM
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
- -> Gateway command parser
46
- -> Existing VCM backend services
47
- -> Gateway reply / notification
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 should be outbound-first from the VCM process. It should
51
- long-poll iLink for direct messages and send replies through the iLink send API.
52
- This avoids requiring a public callback URL for the first gateway version.
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-audit-log.ts
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
- Initial read-only commands:
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 <id>
88
- /pending
89
- /help
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
- State-changing commands require:
110
+ Plain text that does not start with `/` is treated as a PM message for the
111
+ current task.
103
112
 
104
- - authenticated allowed Weixin user
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 gateway should call existing backend services instead of writing task files
110
- directly. `/pm <text>` should use the same safe submit semantics used by VCM's
111
- controlled session input path.
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
- ## 5. Gateway Security
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
- Treat gateway authorization and command parsing as high-risk code.
126
+ VCM already has the other required primitives:
116
127
 
117
- Rules:
118
-
119
- - allowlist Weixin users
120
- - reject unknown users
121
- - deduplicate inbound iLink message ids
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
- - an allowed Weixin user can DM `/status` and receive current VCM status
132
- - an unauthorized user is rejected and audited
133
- - `/tasks` and `/pending` return useful summaries
134
- - `/pm <text>` reaches PM through a controlled backend path
135
- - restart does not replay already handled iLink messages
136
- - credentials stay outside connected repos
137
- - state-changing commands are audited
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
- - authenticate and authorize allowed Weixin users
427
- - confirmation-gate state-changing or destructive commands
428
- - audit state-changing commands
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vibe-coding-master",
3
- "version": "0.2.3",
3
+ "version": "0.2.5",
4
4
  "description": "Local GUI session cockpit for Claude Code role sessions.",
5
5
  "type": "module",
6
6
  "files": [
@@ -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 builds, browser checks, E2E tests, release suites, or any validation command that may take long enough for shell-completion callbacks to become unreliable. Do not end the current turn only to wait for a long-running shell callback.
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 end the current turn only to wait for a long-running shell callback.
626
+ Do not start background jobs.
617
627
 
618
- Use a bounded file-backed job instead.
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
- timeout = parse_duration(args.timeout)
1060
- interval = parse_duration(args.interval)
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}