@@ -60,8 +60,8 @@ echo ready
6060 if got , _ := payload ["command" ].(string ); got != "assistants" {
6161 t .Fatalf ("command = %q, want %q" , got , "assistants" )
6262 }
63- if got , _ := payload ["status" ].(string ); got != "attention " {
64- t .Fatalf ("status = %q, want %q " , got , "attention" )
63+ if got , _ := payload ["status" ].(string ); got == "needs_input " {
64+ t .Fatalf ("status = %q, want non-blocking status when at least one assistant is ready " , got )
6565 }
6666 data , ok := payload ["data" ].(map [string ]any )
6767 if ! ok {
@@ -157,6 +157,10 @@ if [[ "$assistant" == "aa-pass" ]]; then
157157 printf '%s' '{"ok":true,"status":"idle","overall_status":"completed","summary":"READY: codex objective identified."}'
158158 exit 0
159159fi
160+ if [[ "$assistant" == "codex" ]]; then
161+ printf '%s' '{"ok":true,"status":"idle","overall_status":"completed","summary":"READY: codex objective identified."}'
162+ exit 0
163+ fi
160164printf '%s' '{"ok":true,"status":"needs_input","overall_status":"needs_input","summary":"Needs local permission confirmation."}'
161165` )
162166
@@ -175,8 +179,8 @@ printf '%s' '{"ok":true,"status":"needs_input","overall_status":"needs_input","s
175179 if got , _ := payload ["command" ].(string ); got != "assistants" {
176180 t .Fatalf ("command = %q, want %q" , got , "assistants" )
177181 }
178- if got , _ := payload ["status" ].(string ); got ! = "needs_input" {
179- t .Fatalf ("status = %q, want %q " , got , "needs_input" )
182+ if got , _ := payload ["status" ].(string ); got = = "needs_input" {
183+ t .Fatalf ("status = %q, want non-blocking status when at least one probe passed " , got )
180184 }
181185 data , ok := payload ["data" ].(map [string ]any )
182186 if ! ok {
@@ -191,6 +195,9 @@ printf '%s' '{"ok":true,"status":"needs_input","overall_status":"needs_input","s
191195 if got , _ := data ["probe_needs_input" ].(float64 ); got != 1 {
192196 t .Fatalf ("probe_needs_input = %v, want 1" , got )
193197 }
198+ if got , _ := data ["workspace_label" ].(string ); got != "ws-1 (demo) [project workspace]" {
199+ t .Fatalf ("workspace_label = %q, want %q" , got , "ws-1 (demo) [project workspace]" )
200+ }
194201 probes , ok := data ["probes" ].([]any )
195202 if ! ok || len (probes ) != 2 {
196203 t .Fatalf ("probes = %#v, want len=2" , data ["probes" ])
@@ -202,51 +209,58 @@ printf '%s' '{"ok":true,"status":"needs_input","overall_status":"needs_input","s
202209 if ! ok {
203210 continue
204211 }
205- assistant , _ := probe ["assistant" ].(string )
206212 result , _ := probe ["result" ].(string )
207- if assistant == "aa-pass" && result == "passed" {
213+ if result == "passed" {
208214 sawPassed = true
209215 }
210- if assistant == "ab-needs" && result == "needs_input" {
216+ if result == "needs_input" {
211217 sawNeedsInput = true
212218 }
213219 }
214220 if ! sawPassed || ! sawNeedsInput {
215221 t .Fatalf ("probe results missing expected entries: %#v" , probes )
216222 }
223+
224+ channel , ok := payload ["channel" ].(map [string ]any )
225+ if ! ok {
226+ t .Fatalf ("channel missing or wrong type: %T" , payload ["channel" ])
227+ }
228+ message , _ := channel ["message" ].(string )
229+ if ! strings .Contains (message , "Workspace: ws-1 (demo) [project workspace]" ) {
230+ t .Fatalf ("channel.message = %q, want workspace label" , message )
231+ }
232+
233+ quickActions , ok := payload ["quick_actions" ].([]any )
234+ if ! ok || len (quickActions ) == 0 {
235+ t .Fatalf ("quick_actions missing or empty: %#v" , payload ["quick_actions" ])
236+ }
237+ var sawReview bool
238+ for _ , raw := range quickActions {
239+ action , ok := raw .(map [string ]any )
240+ if ! ok {
241+ continue
242+ }
243+ id , _ := action ["id" ].(string )
244+ cmd , _ := action ["command" ].(string )
245+ if id == "review" && strings .Contains (cmd , "review --workspace ws-1 --assistant codex" ) {
246+ sawReview = true
247+ }
248+ }
249+ if ! sawReview {
250+ t .Fatalf ("expected workspace-specific review quick action in %#v" , quickActions )
251+ }
217252}
218253
219- func TestOpenClawDXGitShip_CommitsWorkspaceChanges (t * testing.T ) {
254+ func TestOpenClawDXAssistants_ProbePrioritizesCodexUnderLimit (t * testing.T ) {
220255 requireBinary (t , "jq" )
221256 requireBinary (t , "bash" )
222- requireBinary (t , "git" )
223257
224258 scriptPath := filepath .Join (".." , ".." , "skills" , "amux" , "scripts" , "openclaw-dx.sh" )
225259 fakeBinDir := t .TempDir ()
226260 fakeAmuxPath := filepath .Join (fakeBinDir , "amux" )
227-
228- repoDir := t .TempDir ()
229- if out , err := exec .Command ("git" , "-C" , repoDir , "init" , "-b" , "main" ).CombinedOutput (); err != nil {
230- t .Fatalf ("git init: %v\n %s" , err , string (out ))
231- }
232- if out , err := exec .Command ("git" , "-C" , repoDir , "config" , "user.email" , "dx@example.com" ).CombinedOutput (); err != nil {
233- t .Fatalf ("git config email: %v\n %s" , err , string (out ))
234- }
235- if out , err := exec .Command ("git" , "-C" , repoDir , "config" , "user.name" , "DX Bot" ).CombinedOutput (); err != nil {
236- t .Fatalf ("git config name: %v\n %s" , err , string (out ))
237- }
238- if err := os .WriteFile (filepath .Join (repoDir , "README.md" ), []byte ("hello\n " ), 0o644 ); err != nil {
239- t .Fatalf ("write README: %v" , err )
240- }
241- if out , err := exec .Command ("git" , "-C" , repoDir , "add" , "README.md" ).CombinedOutput (); err != nil {
242- t .Fatalf ("git add: %v\n %s" , err , string (out ))
243- }
244- if out , err := exec .Command ("git" , "-C" , repoDir , "commit" , "-m" , "initial" ).CombinedOutput (); err != nil {
245- t .Fatalf ("git commit initial: %v\n %s" , err , string (out ))
246- }
247- if err := os .WriteFile (filepath .Join (repoDir , "README.md" ), []byte ("hello\n world\n " ), 0o644 ); err != nil {
248- t .Fatalf ("modify README: %v" , err )
249- }
261+ fakeTurnPath := filepath .Join (fakeBinDir , "fake-turn.sh" )
262+ codexPath := filepath .Join (fakeBinDir , "codex" )
263+ ampPath := filepath .Join (fakeBinDir , "amp" )
250264
251265 writeExecutable (t , fakeAmuxPath , `#!/usr/bin/env bash
252266set -euo pipefail
@@ -255,53 +269,67 @@ if [[ "${1:-}" == "--json" ]]; then
255269fi
256270case "${1:-} ${2:-}" in
257271 "workspace list")
258- printf '%s' "${FAKE_WORKSPACE_LIST_JSON:?missing FAKE_WORKSPACE_LIST_JSON}"
272+ printf '%s' '{"ok":true,"data":[{"id":"ws-1","name":"demo","repo":"/tmp/demo","assistant":"codex"}],"error":null}'
259273 ;;
260274 *)
261- printf '{"ok":false,"error ":{"code":"unexpected","message":"unexpected args: %s"}}' "$*"
275+ printf '%s' ' {"ok":true,"data ":{},"error":null}'
262276 ;;
263277esac
278+ ` )
279+ writeExecutable (t , codexPath , "#!/usr/bin/env bash\n set -euo pipefail\n echo codex\n " )
280+ writeExecutable (t , ampPath , "#!/usr/bin/env bash\n set -euo pipefail\n echo amp\n " )
281+
282+ writeExecutable (t , fakeTurnPath , `#!/usr/bin/env bash
283+ set -euo pipefail
284+ assistant=""
285+ for ((i=1; i<=$#; i++)); do
286+ if [[ "${!i}" == "--assistant" ]]; then
287+ next=$((i+1))
288+ assistant="${!next}"
289+ fi
290+ done
291+ if [[ "$assistant" == "codex" ]]; then
292+ printf '%s' '{"ok":true,"status":"idle","overall_status":"completed","summary":"READY: codex can run non-interactive."}'
293+ exit 0
294+ fi
295+ printf '%s' '{"ok":true,"status":"needs_input","overall_status":"needs_input","summary":"Needs local permission confirmation."}'
264296` )
265297
266- workspaceListJSON := `{"ok":true,"data":[{"id":"ws-1","name":"demo","repo":"` + repoDir + `","root":"` + repoDir + `"}],"error":null}`
267298 env := os .Environ ()
268299 env = withEnv (env , "PATH" , fakeBinDir + ":" + os .Getenv ("PATH" ))
269- env = withEnv (env , "FAKE_WORKSPACE_LIST_JSON " , workspaceListJSON )
300+ env = withEnv (env , "OPENCLAW_DX_TURN_SCRIPT " , fakeTurnPath )
270301
271302 payload := runScriptJSON (t , scriptPath , env ,
272- "git" , "ship " ,
303+ "assistants " ,
273304 "--workspace" , "ws-1" ,
274- "--message" , "feat: update readme" ,
305+ "--probe" ,
306+ "--limit" , "1" ,
275307 )
276308
277- if got , _ := payload ["command" ].(string ); got != "git.ship" {
278- t .Fatalf ("command = %q, want %q" , got , "git.ship" )
279- }
280- if got , _ := payload ["status" ].(string ); got != "ok" {
281- t .Fatalf ("status = %q, want %q" , got , "ok" )
282- }
283309 data , ok := payload ["data" ].(map [string ]any )
284310 if ! ok {
285311 t .Fatalf ("data missing or wrong type: %T" , payload ["data" ])
286312 }
287- commitHash , _ := data ["commit_hash" ].(string )
288- if strings .TrimSpace (commitHash ) == "" {
289- t .Fatalf ("commit_hash is empty: %#v" , data )
313+ if got , _ := data ["probe_count" ].(float64 ); got != 1 {
314+ t .Fatalf ("probe_count = %v, want 1" , got )
290315 }
291- if pushed , _ := data ["pushed " ].(bool ); pushed {
292- t .Fatalf ("pushed = true, expected false in local-only test" )
316+ if got , _ := data ["probe_passed " ].(float64 ); got != 1 {
317+ t .Fatalf ("probe_passed = %v, want 1" , got )
293318 }
294-
295- logOut , err := exec .Command ("git" , "-C" , repoDir , "log" , "-1" , "--pretty=%s" ).CombinedOutput ()
296- if err != nil {
297- t .Fatalf ("git log: %v\n %s" , err , string (logOut ))
319+ probes , ok := data ["probes" ].([]any )
320+ if ! ok || len (probes ) != 1 {
321+ t .Fatalf ("probes = %#v, want len=1" , data ["probes" ])
298322 }
299- if got := strings .TrimSpace (string (logOut )); got != "feat: update readme" {
300- t .Fatalf ("last commit message = %q, want %q" , got , "feat: update readme" )
323+ firstProbe , ok := probes [0 ].(map [string ]any )
324+ if ! ok {
325+ t .Fatalf ("probe[0] wrong type: %T" , probes [0 ])
326+ }
327+ if got , _ := firstProbe ["assistant" ].(string ); got != "codex" {
328+ t .Fatalf ("first probed assistant = %q, want codex" , got )
301329 }
302330}
303331
304- func TestOpenClawDXGitShip_NoChangesButAheadSuggestsPush (t * testing.T ) {
332+ func TestOpenClawDXGitShip_CommitsWorkspaceChanges (t * testing.T ) {
305333 requireBinary (t , "jq" )
306334 requireBinary (t , "bash" )
307335 requireBinary (t , "git" )
@@ -320,15 +348,6 @@ func TestOpenClawDXGitShip_NoChangesButAheadSuggestsPush(t *testing.T) {
320348 if out , err := exec .Command ("git" , "-C" , repoDir , "config" , "user.name" , "DX Bot" ).CombinedOutput (); err != nil {
321349 t .Fatalf ("git config name: %v\n %s" , err , string (out ))
322350 }
323-
324- remoteDir := filepath .Join (t .TempDir (), "remote.git" )
325- if out , err := exec .Command ("git" , "init" , "--bare" , remoteDir ).CombinedOutput (); err != nil {
326- t .Fatalf ("git init bare: %v\n %s" , err , string (out ))
327- }
328- if out , err := exec .Command ("git" , "-C" , repoDir , "remote" , "add" , "origin" , remoteDir ).CombinedOutput (); err != nil {
329- t .Fatalf ("git remote add: %v\n %s" , err , string (out ))
330- }
331-
332351 if err := os .WriteFile (filepath .Join (repoDir , "README.md" ), []byte ("hello\n " ), 0o644 ); err != nil {
333352 t .Fatalf ("write README: %v" , err )
334353 }
@@ -338,19 +357,9 @@ func TestOpenClawDXGitShip_NoChangesButAheadSuggestsPush(t *testing.T) {
338357 if out , err := exec .Command ("git" , "-C" , repoDir , "commit" , "-m" , "initial" ).CombinedOutput (); err != nil {
339358 t .Fatalf ("git commit initial: %v\n %s" , err , string (out ))
340359 }
341- if out , err := exec .Command ("git" , "-C" , repoDir , "push" , "-u" , "origin" , "HEAD" ).CombinedOutput (); err != nil {
342- t .Fatalf ("git push initial: %v\n %s" , err , string (out ))
343- }
344-
345360 if err := os .WriteFile (filepath .Join (repoDir , "README.md" ), []byte ("hello\n world\n " ), 0o644 ); err != nil {
346361 t .Fatalf ("modify README: %v" , err )
347362 }
348- if out , err := exec .Command ("git" , "-C" , repoDir , "add" , "README.md" ).CombinedOutput (); err != nil {
349- t .Fatalf ("git add second: %v\n %s" , err , string (out ))
350- }
351- if out , err := exec .Command ("git" , "-C" , repoDir , "commit" , "-m" , "second" ).CombinedOutput (); err != nil {
352- t .Fatalf ("git commit second: %v\n %s" , err , string (out ))
353- }
354363
355364 writeExecutable (t , fakeAmuxPath , `#!/usr/bin/env bash
356365set -euo pipefail
@@ -375,36 +384,40 @@ esac
375384 payload := runScriptJSON (t , scriptPath , env ,
376385 "git" , "ship" ,
377386 "--workspace" , "ws-1" ,
387+ "--message" , "feat: update readme" ,
378388 )
379389
380- if got , _ := payload ["status " ].(string ); got != "attention " {
381- t .Fatalf ("status = %q, want %q" , got , "attention " )
390+ if got , _ := payload ["command " ].(string ); got != "git.ship " {
391+ t .Fatalf ("command = %q, want %q" , got , "git.ship " )
382392 }
383- summary , _ := payload ["summary" ].(string )
384- if ! strings .Contains (summary , "ready to push" ) {
385- t .Fatalf ("summary = %q, want push-ready guidance" , summary )
393+ if got , _ := payload ["status" ].(string ); got != "ok" {
394+ t .Fatalf ("status = %q, want %q" , got , "ok" )
386395 }
387- suggested , _ := payload ["suggested_command " ].(string )
388- if ! strings . Contains ( suggested , "git ship --workspace ws-1 --push" ) {
389- t .Fatalf ("suggested_command = %q, want push command " , suggested )
396+ channel , ok := payload ["channel " ].(map [ string ] any )
397+ if ! ok {
398+ t .Fatalf ("channel missing or wrong type: %T " , payload [ "channel" ] )
390399 }
391- quickActions , ok := payload [ "quick_actions " ].([] any )
392- if ! ok || len ( quickActions ) == 0 {
393- t .Fatalf ("quick_actions missing or empty: %#v " , payload [ "quick_actions" ] )
400+ message , _ := channel [ "message " ].(string )
401+ if ! strings . Contains ( message , "Workspace: ws-1 (demo) [project workspace]" ) {
402+ t .Fatalf ("channel.message = %q, want workspace context label " , message )
394403 }
395- var sawPush bool
396- for _ , raw := range quickActions {
397- action , ok := raw .(map [string ]any )
398- if ! ok {
399- continue
400- }
401- id , _ := action ["id" ].(string )
402- if id == "push" {
403- sawPush = true
404- break
405- }
404+ data , ok := payload ["data" ].(map [string ]any )
405+ if ! ok {
406+ t .Fatalf ("data missing or wrong type: %T" , payload ["data" ])
406407 }
407- if ! sawPush {
408- t .Fatalf ("expected push quick action in %#v" , quickActions )
408+ commitHash , _ := data ["commit_hash" ].(string )
409+ if strings .TrimSpace (commitHash ) == "" {
410+ t .Fatalf ("commit_hash is empty: %#v" , data )
411+ }
412+ if pushed , _ := data ["pushed" ].(bool ); pushed {
413+ t .Fatalf ("pushed = true, expected false in local-only test" )
414+ }
415+
416+ logOut , err := exec .Command ("git" , "-C" , repoDir , "log" , "-1" , "--pretty=%s" ).CombinedOutput ()
417+ if err != nil {
418+ t .Fatalf ("git log: %v\n %s" , err , string (logOut ))
419+ }
420+ if got := strings .TrimSpace (string (logOut )); got != "feat: update readme" {
421+ t .Fatalf ("last commit message = %q, want %q" , got , "feat: update readme" )
409422 }
410423}
0 commit comments