Skip to content

Commit dfa3137

Browse files
author
SqlRush
committed
Accept action-style script steps
1 parent bd9071d commit dfa3137

4 files changed

Lines changed: 144 additions & 0 deletions

File tree

docs/cc-100-roadmap.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -438,6 +438,8 @@ M7 补充:prompt history `LogEntry` 读取现在接受 `sessionID`/`session`/`
438438

439439
本轮补充:interaction script key input 现在接受 press-style aliases,包括 `press``keyPress``keypress``shortcutKey``presses``keyPresses``shortcuts`
440440

441+
本轮补充:interaction script step 现在接受 `action`/`type`/`kind`/`name`/`operation` 动作判别别名,并可用 `value`/`payload`/`data` 等载荷字段驱动 key press、key sequence、text input、paste、status、resize、mouse/image 和 focus/blur 动作。
442+
441443
本轮补充:interaction script step 接受 `resize`/`terminalSize`/`screenSize` 对象或 `[width,height]` 数组、顶层 `columns`/`rows` resize 别名、`focus`/`focused`/`blur`/`focusIn`/`focusOut` focus event 别名、`snapshot`/`snapshotId`/`snapshotLabel` capture 名称别名,以及 runtime-aware mutation 别名如 `permission`/`permissionRequest``task`/`taskStatus``removeTask`/`deleteTask``cancelPermission``cancelTasks`/`cancelReason``openTasks`/`showTasks`
442444

443445
本轮补充:interaction script step 可通过 `status`/`setStatus`/`statusLine`/`baseStatus` 设置状态行;runtime-aware scripts 会把它作为 base status,并继续叠加 permission/task 计数,便于复用带状态栏的 ANSI/interaction fixture。

docs/first-second-parity-audit.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -308,6 +308,7 @@ M7 progress now includes:
308308
- `internal/tui`: interaction script JSONL loading now allows 50MiB records so large paste/image/snapshot fixture lines do not hit scanner token limits.
309309
- `internal/tui`: snapshot corpus comparison now accepts `.ansi`-only baselines by stripping ANSI text on load, and strict unexpected-baseline checks include both `.txt` and `.ansi`.
310310
- `internal/contracts`/`internal/session`: content block decoding now accepts text aliases such as `body`/`message`/`value`/`output`/`contentText`/`content_text` and string `content` for `text`/`thinking` blocks, so transcript resume preserves these nested block variants.
311+
- `internal/tui`: interaction script steps now accept `action`/`type`/`kind`/`name`/`operation` discriminator aliases for common actions such as key press, key sequences, text input, paste, status updates, resize, mouse/image, and focus/blur.
311312

312313
Still missing for full M6/M7 parity:
313314

internal/tui/script_aliases.go

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package tui
22

33
import (
44
"encoding/json"
5+
"strings"
56

67
"ccgo/internal/contracts"
78
"ccgo/internal/session"
@@ -844,9 +845,125 @@ func (step *ScriptStep) UnmarshalJSON(data []byte) error {
844845
if fields.ExpectSnapshotNotCamel != nil {
845846
step.ExpectSnapshotNotContains = stringListValue(fields.ExpectSnapshotNotCamel)
846847
}
848+
applyScriptStepActionAlias(step, fieldMap)
847849
return nil
848850
}
849851

852+
func applyScriptStepActionAlias(step *ScriptStep, fields map[string]json.RawMessage) {
853+
action := canonicalScriptStepAction(stringJSONField(fields,
854+
"action",
855+
"step_action",
856+
"stepAction",
857+
"operation",
858+
"op",
859+
"command",
860+
"kind",
861+
"name",
862+
"type",
863+
))
864+
switch action {
865+
case "key", "press", "keypress", "key-press", "shortcut", "shortcut-key":
866+
if step.Key == "" && len(step.Keys) == 0 {
867+
if values := stringListJSONField(fields, "value", "payload", "data", "key", "keys", "shortcut", "sequence", "input", "text"); len(values) == 1 {
868+
step.Key = values[0]
869+
} else if len(values) > 1 {
870+
step.Keys = append(step.Keys, values...)
871+
}
872+
}
873+
case "keys", "presses", "shortcuts", "sequence", "key-sequence":
874+
if step.Key == "" && len(step.Keys) == 0 {
875+
step.Keys = append(step.Keys, stringListJSONField(fields, "value", "payload", "data", "keys", "sequence", "key_sequence", "keySequence", "shortcuts")...)
876+
}
877+
case "text", "type", "type-text", "input", "insert", "write":
878+
if step.Text == "" {
879+
step.Text = stringJSONField(fields, "value", "text", "input", "content", "body", "message", "data", "payload")
880+
}
881+
case "paste", "clipboard":
882+
if step.Paste == "" {
883+
step.Paste = stringJSONField(fields, "value", "paste", "clipboard", "text", "content", "data", "payload")
884+
}
885+
case "status", "set-status", "status-line":
886+
if step.Status == "" {
887+
step.Status = stringJSONField(fields, "value", "status", "text", "content", "message", "data", "payload")
888+
}
889+
case "snapshot", "capture", "capture-snapshot":
890+
if step.SnapshotName == "" {
891+
step.SnapshotName = stringJSONField(fields, "value", "snapshot", "name", "label", "id", "data", "payload")
892+
}
893+
case "resize", "terminal-size", "screen-size":
894+
if step.ResizeWidth <= 0 || step.ResizeHeight <= 0 {
895+
if size := scriptSizeJSONField(fields, "value", "size", "dimensions", "payload", "data"); size != nil {
896+
if step.ResizeWidth <= 0 && size.Width > 0 {
897+
step.ResizeWidth = size.Width
898+
}
899+
if step.ResizeHeight <= 0 && size.Height > 0 {
900+
step.ResizeHeight = size.Height
901+
}
902+
}
903+
}
904+
case "mouse", "mouse-event":
905+
if step.Mouse == nil {
906+
step.Mouse = scriptMouseJSONField(fields, "value", "mouse", "event", "payload", "data")
907+
}
908+
case "image", "paste-image":
909+
if step.Image == nil {
910+
step.Image = scriptImageJSONField(fields, "value", "image", "payload", "data")
911+
}
912+
case "focus", "focus-in":
913+
if !scriptStepHasFocusKey(step) {
914+
step.Keys = append(step.Keys, "focus-in")
915+
}
916+
case "blur", "focus-out":
917+
if !scriptStepHasFocusKey(step) {
918+
step.Keys = append(step.Keys, "focus-out")
919+
}
920+
}
921+
}
922+
923+
func canonicalScriptStepAction(action string) string {
924+
action = strings.ToLower(strings.TrimSpace(action))
925+
action = strings.ReplaceAll(action, "_", "-")
926+
action = strings.ReplaceAll(action, " ", "-")
927+
return action
928+
}
929+
930+
func scriptMouseJSONField(fields map[string]json.RawMessage, names ...string) *ScriptMouse {
931+
for _, name := range names {
932+
raw, ok := fields[name]
933+
if !ok {
934+
continue
935+
}
936+
var mouse ScriptMouse
937+
if err := json.Unmarshal(raw, &mouse); err == nil {
938+
return &mouse
939+
}
940+
}
941+
return nil
942+
}
943+
944+
func scriptImageJSONField(fields map[string]json.RawMessage, names ...string) *ScriptImage {
945+
for _, name := range names {
946+
raw, ok := fields[name]
947+
if !ok {
948+
continue
949+
}
950+
var image ScriptImage
951+
if err := json.Unmarshal(raw, &image); err == nil {
952+
return &image
953+
}
954+
}
955+
return nil
956+
}
957+
958+
func scriptStepHasFocusKey(step *ScriptStep) bool {
959+
for _, key := range step.Keys {
960+
if key == "focus-in" || key == "focus-out" {
961+
return true
962+
}
963+
}
964+
return step.Key == "focus-in" || step.Key == "focus-out"
965+
}
966+
850967
func normalizeScriptStepJSON(data []byte) []byte {
851968
data = normalizeStringFieldsToArray(data,
852969
"keys",

internal/tui/tui_test.go

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4921,6 +4921,30 @@ func TestRunInteractionScriptAcceptsPressFieldAliases(t *testing.T) {
49214921
}
49224922
}
49234923

4924+
func TestRunInteractionScriptAcceptsActionDiscriminatorAliases(t *testing.T) {
4925+
steps, err := ParseInteractionScript([]byte(`[
4926+
{"action":"type","value":"hi","expectPrompt":{"text":"hi"}},
4927+
{"type":"press","payload":"enter","expectEvent":{"type":"prompt_submitted","value":"hi"},"expectPrompt":{"empty":true}},
4928+
{"kind":"keys","data":["ctrl-x","ctrl-k"],"expectEvent":{"type":"kill_agents"}},
4929+
{"name":"paste","payload":"clip","expectPrompt":{"text":"[Pasted text #1]","expandedText":"clip","pastedContentCount":1}},
4930+
{"operation":"status","value":"busy","expectStatusContains":"busy"},
4931+
{"action":"resize","value":[50,9],"expectScreen":{"columns":50,"rows":9}}
4932+
]`))
4933+
if err != nil {
4934+
t.Fatal(err)
4935+
}
4936+
screen := NewREPLScreen(40, 8, nil)
4937+
result, err := RunInteractionScriptChecked(&screen, steps)
4938+
if err != nil {
4939+
t.Fatal(err)
4940+
}
4941+
if len(result.Events) != 2 ||
4942+
result.Events[0].Type != ScreenEventPromptSubmitted || result.Events[0].Value != "hi" ||
4943+
result.Events[1].Type != ScreenEventKillAgents {
4944+
t.Fatalf("events = %#v", result.Events)
4945+
}
4946+
}
4947+
49244948
func TestRunInteractionScriptAppliesStepKeybindings(t *testing.T) {
49254949
steps, err := ParseInteractionScript([]byte(`[
49264950
{"keybindings":{"keys":"ctrl-r","command":"submitPrompt"}},

0 commit comments

Comments
 (0)