Skip to content

Commit 398116d

Browse files
author
SqlRush
committed
Accept runtime script action aliases
1 parent 2499b3d commit 398116d

5 files changed

Lines changed: 235 additions & 0 deletions

File tree

docs/cc-100-roadmap.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -184,6 +184,8 @@ M7 补充:interaction script step 现在接受 `resize`/`terminalSize`/`screen
184184

185185
M7 补充:interaction script action/type/kind/name/operation 动作判别字段现在接受 compact/camel event/media aliases,包括 `focusIn``focusOut``mouseEvent``pasteImage``imagePaste`
186186

187+
M7 补充:interaction script action/type/kind/name/operation 动作判别字段现在可驱动 runtime/dialog mutation,支持 `requestPermission``taskStatus``showTasks``cancelTasks``removeTask``showDialog` 等动作,并从 `value`/`payload`/`data`/`body` 载荷解析对象、ID 或取消原因。
188+
187189
M7 补充:interaction script JSONL loader 单行上限提升到 50MiB,和 transcript/session 大记录读取容忍度对齐,避免大型 paste、image metadata 或 snapshot fixture 脚本行触发 scanner token limit。
188190

189191
M7 补充:terminal CSI-u/kitty keyboard parser 现在接受 codepoint alternate 和 modifier event-type 的冒号字段(如 `CSI 97:65;5:1u`),按主 codepoint/modifier 解析 ctrl/alt/shift/rune 键,避免 kitty progressive keyboard protocol 变体被判为 unknown。
@@ -514,6 +516,8 @@ M7 补充:prompt history `LogEntry` 读取现在接受 `sessionID`/`session`/`
514516

515517
本轮补充:interaction script action/type/kind/name/operation 动作判别字段现在也接受 compact/camel event/media aliases,包括 `focusIn``focusOut``mouseEvent``pasteImage``imagePaste`
516518

519+
本轮补充:interaction script action/type/kind/name/operation 动作判别字段现在也能驱动 runtime/dialog mutation,支持 `requestPermission``taskStatus``showTasks``cancelTasks``removeTask``showDialog` 等动作,并从 `value`/`payload`/`data`/`body` 载荷解析 permission/task/dialog 对象、task/permission ID 或取消原因。
520+
517521
本轮补充: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`
518522

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

docs/claude-code-go-rewrite-plan.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -274,6 +274,7 @@ test/parity/ # golden tests against TS/official behavior
274274
- 本轮补充:interaction script key input 接受 press-style aliases,包括 `press``keyPress``keypress``shortcutKey``presses``keyPresses``shortcuts`
275275
- 本轮补充:interaction script action/type/kind/name/operation 动作判别字段接受 compact/camel fixture aliases,包括 `typeText``inputText``insertText``keyPress``pressKey``keySequence``pasteText``pastedText``clipboardText``setStatus``statusLine``terminalSize``screenSize`
276276
- 本轮补充:interaction script action/type/kind/name/operation 动作判别字段继续接受 compact/camel event/media aliases,包括 `focusIn``focusOut``mouseEvent``pasteImage``imagePaste`
277+
- 本轮补充:interaction script action/type/kind/name/operation 动作判别字段也可驱动 runtime/dialog mutation,支持 `requestPermission``taskStatus``showTasks``cancelTasks``removeTask``showDialog` 等动作,并从 `value`/`payload`/`data`/`body` 载荷解析对象、ID 或取消原因。
277278
- 本轮补充:terminal CSI-u/kitty keyboard parser 接受 `codepoint:alternate``modifier:event-type` 冒号字段,按主 codepoint/modifier 解析 ctrl/alt/shift/rune 键,覆盖 kitty progressive keyboard protocol 的常见变体。
278279
- 本轮补充:terminal CSI-u/kitty keyboard parser 接受无 modifier 字段或 modifier `1` 的 base key 序列,覆盖 printable rune、Enter、Tab、Esc 和 Backspace,避免 extended-key 模式下普通键序列被解析成 unknown。
279280
- 本轮补充:terminal input parser 接受 xterm modified arrow 序列(如 `CSI 1;2D``CSI 1;6C``CSI 1;7D`),把 shift-arrow 降级为方向键、alt-arrow 映射到 word-motion key、ctrl/ctrl+alt-arrow 映射到 ctrl word-motion key,避免 extended navigation 序列落入 unknown。

docs/first-second-parity-audit.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -331,6 +331,7 @@ M7 progress now includes:
331331
- `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.
332332
- `internal/tui`: interaction script action discriminators now accept compact/camel fixture names such as `typeText`, `inputText`, `insertText`, `keyPress`, `pressKey`, `keySequence`, `pasteText`, `pastedText`, `clipboardText`, `setStatus`, `statusLine`, `terminalSize`, and `screenSize`.
333333
- `internal/tui`: interaction script action discriminators now also accept compact event/media fixture names such as `focusIn`, `focusOut`, `mouseEvent`, `pasteImage`, and `imagePaste`.
334+
- `internal/tui`: interaction script action discriminators can now drive runtime/dialog mutations such as `requestPermission`, `taskStatus`, `showTasks`, `cancelTasks`, `removeTask`, and `showDialog`, using `value`/`payload`/`data`/`body` payloads for objects, IDs, and cancel reasons.
334335
- `internal/contracts`/`internal/session`: image content blocks now normalize source aliases such as `mediaType`/`mimeType`/`contentType` and `base64`/`payload`, including top-level image block fields, into canonical `ImageSource` values during transcript resume.
335336
- `internal/tui`: interaction script single-step JSON now unwraps `step`/`scriptStep`/`interactionStep`/`record`/`entry`/`item`/`event` objects in arrays, JSONL records, and wrapper-object step lists.
336337
- `internal/tui`: interaction script top-level wrappers now accept record-array aliases such as `records`/`recordedSteps`/`events`/`entries`/`items`/`actions`/`timeline`.

internal/tui/script_aliases.go

Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1019,6 +1019,37 @@ func applyScriptStepActionAlias(step *ScriptStep, fields map[string]json.RawMess
10191019
if step.Image == nil {
10201020
step.Image = scriptImageJSONField(fields, "value", "image", "payload", "data")
10211021
}
1022+
case "permission", "permissionrequest", "permission-request", "requestpermission", "request-permission":
1023+
if step.RequestPermission == nil {
1024+
step.RequestPermission = scriptPermissionRequestActionField(fields)
1025+
}
1026+
case "task", "taskstatus", "task-status", "upserttask", "upsert-task":
1027+
if step.UpsertTask == nil {
1028+
step.UpsertTask = scriptTaskStatusActionField(fields)
1029+
}
1030+
case "removetask", "remove-task", "deletetask", "delete-task":
1031+
if step.RemoveTaskID == "" {
1032+
step.RemoveTaskID = scriptActionIDField(fields, "task_id", "taskId", "taskID", "id")
1033+
}
1034+
case "opentasks", "open-tasks", "opentasksdialog", "open-tasks-dialog", "showtasks", "show-tasks":
1035+
step.OpenTasksDialog = scriptActionBoolField(fields, true)
1036+
case "cancelactivedialog", "cancel-active-dialog", "canceldialog", "cancel-dialog", "closedialog", "close-dialog":
1037+
step.CancelActiveDialog = scriptActionBoolField(fields, true)
1038+
case "cancelpermission", "cancel-permission":
1039+
if step.CancelPermissionID == "" {
1040+
step.CancelPermissionID = scriptActionIDField(fields, "permission_id", "permissionId", "permissionID", "request_id", "requestId", "requestID", "dialog_id", "dialogId", "dialogID", "id")
1041+
}
1042+
case "cancelpermissions", "cancel-permissions", "cancelallpermissions", "cancel-all-permissions":
1043+
step.CancelAllPermissions = scriptActionBoolField(fields, true)
1044+
case "canceltasks", "cancel-tasks", "cancelalltasks", "cancel-all-tasks":
1045+
step.CancelAllTasks = scriptActionBoolField(fields, true)
1046+
if step.CancelTasksDetail == "" {
1047+
step.CancelTasksDetail = scriptActionStringField(fields, "reason", "detail", "message", "description")
1048+
}
1049+
case "dialog", "showdialog", "show-dialog", "opendialog", "open-dialog":
1050+
if step.Dialog == nil {
1051+
step.Dialog = scriptDialogActionField(fields)
1052+
}
10221053
case "focus", "focusin", "focus-in":
10231054
if !scriptStepHasFocusKey(step) {
10241055
step.Keys = append(step.Keys, "focus-in")
@@ -1037,6 +1068,125 @@ func canonicalScriptStepAction(action string) string {
10371068
return action
10381069
}
10391070

1071+
func scriptPermissionRequestActionField(fields map[string]json.RawMessage) *PermissionRequest {
1072+
for _, raw := range scriptActionRawFields(fields, "request", "permission", "permission_request", "permissionRequest") {
1073+
var request PermissionRequest
1074+
if err := json.Unmarshal(raw, &request); err == nil {
1075+
return &request
1076+
}
1077+
}
1078+
var request PermissionRequest
1079+
if scriptUnmarshalStepFields(fields, &request) && scriptPermissionRequestHasData(request) {
1080+
return &request
1081+
}
1082+
return nil
1083+
}
1084+
1085+
func scriptPermissionRequestHasData(request PermissionRequest) bool {
1086+
return request.ID != "" || request.ToolName != "" || request.Path != "" || request.Description != "" || len(request.Actions) > 0
1087+
}
1088+
1089+
func scriptTaskStatusActionField(fields map[string]json.RawMessage) *TaskStatus {
1090+
for _, raw := range scriptActionRawFields(fields, "task", "task_status", "taskStatus") {
1091+
var task TaskStatus
1092+
if err := json.Unmarshal(raw, &task); err == nil {
1093+
return &task
1094+
}
1095+
}
1096+
var task TaskStatus
1097+
if scriptUnmarshalStepFields(fields, &task) && scriptTaskStatusHasData(task) {
1098+
return &task
1099+
}
1100+
return nil
1101+
}
1102+
1103+
func scriptTaskStatusHasData(task TaskStatus) bool {
1104+
return task.ID != "" || task.Title != "" || task.State != "" || task.Detail != "" || task.Progress != 0
1105+
}
1106+
1107+
func scriptDialogActionField(fields map[string]json.RawMessage) *Dialog {
1108+
for _, raw := range scriptActionRawFields(fields, "dialog") {
1109+
var dialog Dialog
1110+
if err := json.Unmarshal(raw, &dialog); err == nil {
1111+
return &dialog
1112+
}
1113+
}
1114+
var dialog Dialog
1115+
if scriptUnmarshalStepFields(fields, &dialog) && scriptDialogHasData(dialog) {
1116+
return &dialog
1117+
}
1118+
return nil
1119+
}
1120+
1121+
func scriptDialogHasData(dialog Dialog) bool {
1122+
return dialog.ID != "" || dialog.Kind != "" || dialog.Title != "" || dialog.Body != "" || len(dialog.Actions) > 0 || dialog.Focused != 0
1123+
}
1124+
1125+
func scriptActionRawFields(fields map[string]json.RawMessage, names ...string) []json.RawMessage {
1126+
allNames := append([]string{"value", "payload", "data", "body"}, names...)
1127+
raws := make([]json.RawMessage, 0, len(allNames))
1128+
for _, name := range allNames {
1129+
raw, ok := fields[name]
1130+
if !ok {
1131+
continue
1132+
}
1133+
raws = append(raws, raw)
1134+
}
1135+
return raws
1136+
}
1137+
1138+
func scriptUnmarshalStepFields(fields map[string]json.RawMessage, target any) bool {
1139+
raw, err := json.Marshal(fields)
1140+
if err != nil {
1141+
return false
1142+
}
1143+
return json.Unmarshal(raw, target) == nil
1144+
}
1145+
1146+
func scriptActionIDField(fields map[string]json.RawMessage, idNames ...string) string {
1147+
return scriptActionStringField(fields, idNames...)
1148+
}
1149+
1150+
func scriptActionStringField(fields map[string]json.RawMessage, objectNames ...string) string {
1151+
if value := stringJSONField(fields, append([]string{"value", "payload", "data", "body"}, objectNames...)...); value != "" {
1152+
return value
1153+
}
1154+
for _, raw := range scriptActionRawFields(fields) {
1155+
raw = bytes.TrimSpace(raw)
1156+
if len(raw) == 0 || raw[0] != '{' {
1157+
continue
1158+
}
1159+
nested := map[string]json.RawMessage{}
1160+
if err := json.Unmarshal(raw, &nested); err != nil {
1161+
continue
1162+
}
1163+
if value := stringJSONField(nested, objectNames...); value != "" {
1164+
return value
1165+
}
1166+
}
1167+
return ""
1168+
}
1169+
1170+
func scriptActionBoolField(fields map[string]json.RawMessage, fallback bool) bool {
1171+
if value := boolPtrJSONField(fields, "value", "payload", "data", "enabled", "active", "open"); value != nil {
1172+
return *value
1173+
}
1174+
for _, raw := range scriptActionRawFields(fields) {
1175+
raw = bytes.TrimSpace(raw)
1176+
if len(raw) == 0 || raw[0] != '{' {
1177+
continue
1178+
}
1179+
nested := map[string]json.RawMessage{}
1180+
if err := json.Unmarshal(raw, &nested); err != nil {
1181+
continue
1182+
}
1183+
if value := boolPtrJSONField(nested, "enabled", "active", "open", "value"); value != nil {
1184+
return *value
1185+
}
1186+
}
1187+
return fallback
1188+
}
1189+
10401190
func scriptMouseJSONField(fields map[string]json.RawMessage, names ...string) *ScriptMouse {
10411191
for _, name := range names {
10421192
raw, ok := fields[name]

internal/tui/tui_test.go

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5366,6 +5366,85 @@ func TestParseInteractionScriptAcceptsCompactMouseActionAlias(t *testing.T) {
53665366
}
53675367
}
53685368

5369+
func TestRunDialogRuntimeScriptAcceptsRuntimeActionDiscriminatorAliases(t *testing.T) {
5370+
steps, err := ParseInteractionScript([]byte(`[
5371+
{
5372+
"action":"requestPermission",
5373+
"payload":{"permissionId":"perm_action","tool":"Bash","actions":"Allow"},
5374+
"expectDialog":{"active":true,"id":"perm_action","kind":"permission"},
5375+
"expectStatusContains":"permissions: 1"
5376+
},
5377+
{
5378+
"action":"keyPress",
5379+
"payload":"enter",
5380+
"expectEvent":{"type":"dialog_action","value":"Allow","dialogId":"perm_action","dialogKind":"permission"},
5381+
"expectDialogResult":{"id":"perm_action","status":"allowed","found":true}
5382+
},
5383+
{
5384+
"type":"taskStatus",
5385+
"payload":{"taskId":"task_action","name":"Build","status":"running","statusText":"go test","progressPercent":25},
5386+
"expectTasks":{"count":1,"stateCounts":{"running":1},"contains":{"taskId":"task_action","status":"running","statusText":"go test","progressPercent":25}}
5387+
},
5388+
{
5389+
"kind":"showTasks",
5390+
"expectDialog":{"active":true,"id":"tasks","kind":"task"},
5391+
"expectSnapshotContains":"Build [running] 25% - go test"
5392+
},
5393+
{
5394+
"name":"cancelTasks",
5395+
"payload":{"reason":"stopped"},
5396+
"expectTasks":{"count":1,"stateCounts":{"cancelled":1},"contains":{"taskId":"task_action","status":"cancelled","statusText":"stopped","progressPercent":25}},
5397+
"expectStatusContains":"cancelled: 1"
5398+
},
5399+
{
5400+
"operation":"removeTask",
5401+
"payload":{"taskId":"task_action"},
5402+
"expectTasks":{"count":0}
5403+
}
5404+
]`))
5405+
if err != nil {
5406+
t.Fatal(err)
5407+
}
5408+
screen := NewREPLScreen(52, 9, nil)
5409+
runtime := NewDialogRuntime()
5410+
result, err := RunDialogRuntimeScriptChecked(&screen, runtime, "ready", steps)
5411+
if err != nil {
5412+
t.Fatal(err)
5413+
}
5414+
if len(result.DialogResults) != 1 || result.DialogResults[0].ID != "perm_action" || result.DialogResults[0].Status != DialogResultAllowed {
5415+
t.Fatalf("dialog results = %#v", result.DialogResults)
5416+
}
5417+
if len(runtime.Tasks) != 0 {
5418+
t.Fatalf("tasks = %#v", runtime.Tasks)
5419+
}
5420+
}
5421+
5422+
func TestRunInteractionScriptAcceptsDialogActionDiscriminatorAlias(t *testing.T) {
5423+
steps, err := ParseInteractionScript([]byte(`[
5424+
{
5425+
"action":"showDialog",
5426+
"payload":{"dialogId":"custom_action","dialogKind":"permission","heading":"Custom","content":"Pick one.","options":"Proceed"},
5427+
"expectDialog":{"visible":true,"dialogId":"custom_action","dialogKind":"permission","heading":"Custom","content":"Pick one.","actions":"Proceed"}
5428+
},
5429+
{
5430+
"action":"keyPress",
5431+
"payload":"enter",
5432+
"expectEvent":{"type":"dialog_action","value":"Proceed","dialogId":"custom_action","dialogKind":"permission"}
5433+
}
5434+
]`))
5435+
if err != nil {
5436+
t.Fatal(err)
5437+
}
5438+
screen := NewREPLScreen(40, 8, nil)
5439+
result, err := RunInteractionScriptChecked(&screen, steps)
5440+
if err != nil {
5441+
t.Fatal(err)
5442+
}
5443+
if len(result.Events) != 1 || result.Events[0].DialogID != "custom_action" || result.Events[0].Value != "Proceed" {
5444+
t.Fatalf("events = %#v", result.Events)
5445+
}
5446+
}
5447+
53695448
func TestRunInteractionScriptAppliesStepKeybindings(t *testing.T) {
53705449
steps, err := ParseInteractionScript([]byte(`[
53715450
{"keybindings":{"keys":"ctrl-r","command":"submitPrompt"}},

0 commit comments

Comments
 (0)