@dmsdc-ai/aterm-darwin-arm64 0.1.45 → 0.1.47

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.
Binary file
@@ -5,6 +5,19 @@ set -euo pipefail
5
5
 
6
6
  ATERM_IPC_SOCKET="${ATERM_IPC_SOCKET:-}"
7
7
 
8
+ detect_ui_lang() {
9
+ local raw="${ATERM_UI_LANG:-${LC_ALL:-${LC_MESSAGES:-${LANG:-}}}}"
10
+ raw="${raw,,}"
11
+ if [[ "$raw" == ko* || "$raw" == *"ko_"* || "$raw" == *"ko-"* ]]; then
12
+ echo "ko"
13
+ else
14
+ echo "en"
15
+ fi
16
+ }
17
+
18
+ ATERM_UI_LANG="$(detect_ui_lang)"
19
+ export ATERM_UI_LANG
20
+
8
21
  # State directory for task board and lessons
9
22
  STATE_DIR="${PWD}/state"
10
23
 
@@ -26,29 +39,175 @@ ensure_lessons_file() {
26
39
  }
27
40
 
28
41
  print_ecosystem_status() {
29
- cat <<'STATUS'
42
+ if [[ "$ATERM_UI_LANG" == "ko" ]]; then
43
+ cat <<'STATUS'
44
+ brain: ✅ 설치됨
45
+ telepty: ✅ 설치됨
46
+ deliberation: ✅ 설치됨
47
+ devkit: ✅ 설치됨
48
+ STATUS
49
+ else
50
+ cat <<'STATUS'
30
51
  brain: ✅ installed
31
52
  telepty: ✅ installed
32
53
  deliberation: ✅ installed
33
54
  devkit: ✅ installed
34
55
  STATUS
56
+ fi
57
+ }
58
+
59
+ show_help() {
60
+ if [[ "$ATERM_UI_LANG" == "ko" ]]; then
61
+ cat <<'HELP'
62
+ # aterm — AI 네이티브 터미널 세션 통신
63
+
64
+ ## 명령어
65
+
66
+ | 명령어 | 설명 |
67
+ |--------|------|
68
+ | `aterm list` | 워크스페이스 목록 표시 (`이름`, `CLI`, `경로`, `터미널`) |
69
+ | `aterm inject <workspace> <text>` | 워크스페이스에 텍스트 전송 |
70
+ | `aterm status` | 번들된 에코시스템 상태 표시 |
71
+ | `aterm status <workspace>` | 워크스페이스 상태 확인 |
72
+ | `aterm focus <workspace>` | 워크스페이스 탭으로 포커스 이동 |
73
+ | `aterm create <name> --cli <cli> --cwd <path>` | 새 워크스페이스 생성 |
74
+ | `aterm restart <workspace>` | 죽은 세션 재시작 |
75
+ | `aterm kill <workspace>` | 워크스페이스 종료 |
76
+ | `aterm restart-all` | 죽은 세션 전체 재시작 |
77
+ | `aterm tasks` | 대기 중인 태스크 목록 표시 |
78
+ | `aterm tasks add '<description>'` | 새 태스크 추가 |
79
+ | `aterm tasks done <id>` | 태스크 완료 처리 |
80
+ | `aterm lessons` | 기록된 교훈 표시 |
81
+ | `aterm lessons add '<lesson>'` | 교훈 추가 (`invariant`) |
82
+ | `aterm lessons add '<lesson>' --type failed` | 실패 패턴 추가 |
83
+ | `aterm dispatch <task-id>` | 태스크 분해 → 서브세션 생성 → 실행 → 수집 → 정리 |
84
+ | `aterm dispatch --plan '<desc>'` | 자유 텍스트 태스크 → 동일한 dispatch 흐름 |
85
+ | `aterm help` | 이 도움말 표시 |
86
+
87
+ ## 에코시스템
88
+
89
+ ✅ brain (AI 프로필 관리) — 이미 설치됨
90
+ ✅ telepty (터미널 간 통신) — 이미 설치됨
91
+ ✅ deliberation (멀티 AI 토론) — 이미 설치됨
92
+ ✅ devkit (워크스페이스 부트스트랩) — 이미 설치됨
93
+
94
+ ## 자연어 → 명령어 매핑
95
+
96
+ ### 내부 세션 (같은 aterm, `$ATERM_IPC_SOCKET` 설정됨)
97
+ | English request | 한국어 요청 | 명령어 |
98
+ |----------------|------------|--------|
99
+ | `list sessions` | `세션 목록 보여줘` | `aterm list` |
100
+ | `send build to ghostty` | `ghostty에 빌드 실행해줘` | `aterm inject ghostty 'make build'` |
101
+ | `check cmux status` | `cmux 세션 상태 확인해줘` | `aterm status cmux` |
102
+ | `show tasks` | `태스크 목록 보여줘` | `aterm tasks` |
103
+ | `add task: implement API` | `태스크 추가해줘: API 구현` | `aterm tasks add 'API 구현'` |
104
+ | `task 42 done` | `태스크 42 완료` | `aterm tasks done 42` |
105
+ | `show lessons` | `레슨 보여줘` | `aterm lessons` |
106
+ | `add lesson: this pattern failed` | `레슨 추가` | `aterm lessons add 'this pattern failed'` |
107
+
108
+ ### 외부 세션 (다른 터미널/머신, `$ATERM_IPC_SOCKET` 미설정)
109
+ | English request | 한국어 요청 | 명령어 |
110
+ |----------------|------------|--------|
111
+ | `list external sessions` | `외부 세션 목록` | `telepty list` |
112
+ | `message deliberation session` | `deliberation 세션에 메시지 보내줘` | `telepty inject aigentry-deliberation-claude 'message'` |
113
+
114
+ ## 세션 탐지 우선순위
115
+ 1. `aterm list` — 내부 세션 (프로젝트 이름 + `terminal=aterm`)
116
+ 2. `telepty list` — 외부 세션 (프로젝트 이름 + 터미널 타입)
117
+ 3. `ps aux | grep` 를 주 탐지 수단으로 사용하지 말 것 (프로젝트 이름과 터미널 정보가 없음)
118
+
119
+ ## 탐지 규칙
120
+ - `$ATERM_IPC_SOCKET` 이 설정되어 있으면 aterm 내부입니다. `aterm` 명령을 사용합니다.
121
+ - `$ATERM_IPC_SOCKET` 이 비어 있으면 aterm 외부입니다. `telepty` 명령을 사용합니다.
122
+ - `aterm` 은 `list`/`inject`/`status` 에서 자동으로 `telepty` 로 폴백합니다.
123
+ HELP
124
+ else
125
+ cat <<'HELP'
126
+ # aterm — AI-native terminal session communication
127
+
128
+ ## Commands
129
+
130
+ | Command | Description |
131
+ |---------|-------------|
132
+ | `aterm list` | List workspaces (`NAME`, `CLI`, `CWD`, `TERMINAL`) |
133
+ | `aterm inject <workspace> <text>` | Send text to a workspace |
134
+ | `aterm status` | Show bundled ecosystem health |
135
+ | `aterm status <workspace>` | Check workspace status |
136
+ | `aterm focus <workspace>` | Focus a workspace tab |
137
+ | `aterm create <name> --cli <cli> --cwd <path>` | Create a new workspace |
138
+ | `aterm restart <workspace>` | Restart a dead session |
139
+ | `aterm kill <workspace>` | Close a workspace |
140
+ | `aterm restart-all` | Restart all dead sessions |
141
+ | `aterm tasks` | List pending tasks |
142
+ | `aterm tasks add '<description>'` | Add a new task |
143
+ | `aterm tasks done <id>` | Mark a task as completed |
144
+ | `aterm lessons` | List recorded lessons |
145
+ | `aterm lessons add '<lesson>'` | Add a lesson (`invariant`) |
146
+ | `aterm lessons add '<lesson>' --type failed` | Add a failed pattern |
147
+ | `aterm dispatch <task-id>` | Break down task -> create sub-sessions -> execute -> collect -> cleanup |
148
+ | `aterm dispatch --plan '<desc>'` | Free-text task -> same dispatch flow |
149
+ | `aterm help` | Show this help |
150
+
151
+ ## Ecosystem
152
+
153
+ ✅ brain (AI profile management) — already installed
154
+ ✅ telepty (cross-terminal communication) — already installed
155
+ ✅ deliberation (multi-AI discussions) — already installed
156
+ ✅ devkit (workspace bootstrapping) — already installed
157
+
158
+ ## Natural Language -> Command Mapping
159
+
160
+ ### Internal sessions (same aterm, `$ATERM_IPC_SOCKET` set)
161
+ | English request | Korean request | Command |
162
+ |----------------|----------------|---------|
163
+ | `list sessions` | `세션 목록 보여줘` | `aterm list` |
164
+ | `send build to ghostty` | `ghostty에 빌드 실행해줘` | `aterm inject ghostty 'make build'` |
165
+ | `check cmux status` | `cmux 세션 상태 확인해줘` | `aterm status cmux` |
166
+ | `show tasks` | `태스크 목록 보여줘` | `aterm tasks` |
167
+ | `add task: implement API` | `태스크 추가해줘: API 구현` | `aterm tasks add 'API 구현'` |
168
+ | `task 42 done` | `태스크 42 완료` | `aterm tasks done 42` |
169
+ | `show lessons` | `레슨 보여줘` | `aterm lessons` |
170
+ | `add lesson: this pattern failed` | `레슨 추가` | `aterm lessons add 'this pattern failed'` |
171
+
172
+ ### External sessions (other terminal or machine, `$ATERM_IPC_SOCKET` unset)
173
+ | English request | Korean request | Command |
174
+ |----------------|----------------|---------|
175
+ | `list external sessions` | `외부 세션 목록` | `telepty list` |
176
+ | `message deliberation session` | `deliberation 세션에 메시지 보내줘` | `telepty inject aigentry-deliberation-claude 'message'` |
177
+
178
+ ## Session Detection Priority
179
+ 1. `aterm list` — internal sessions (project names + `terminal=aterm`)
180
+ 2. `telepty list` — external sessions (project names + terminal type)
181
+ 3. Never use `ps aux | grep` as the primary detection method (no project names, no terminal info)
182
+
183
+ ## Detection Rules
184
+ - `$ATERM_IPC_SOCKET` set -> inside aterm. Use `aterm` commands.
185
+ - `$ATERM_IPC_SOCKET` unset -> outside aterm. Use `telepty` commands.
186
+ - `aterm` automatically falls back to `telepty` for `list`, `inject`, and `status`.
187
+ HELP
188
+ fi
35
189
  }
36
190
 
37
191
  aterm_ipc() {
38
192
  python3 -c '
39
- import socket, sys, json
193
+ import os, socket, sys, json
40
194
 
41
195
  sock_path = sys.argv[1]
42
196
  action = sys.argv[2]
43
197
  args = sys.argv[3:]
198
+ lang = os.environ.get("ATERM_UI_LANG", "en")
44
199
 
45
200
  payloads = {
46
- "list": lambda: {"action": "ListWorkspaces"},
47
- "inject": lambda: {"action": "Inject", "workspace": args[0],
48
- "text": " ".join(args[1:]),
49
- "from": __import__("os").environ.get("ATERM_WORKSPACE_NAME")},
50
- "status": lambda: {"action": "WorkspaceStatus", "workspace": args[0]},
51
- "focus": lambda: {"action": "FocusWorkspace", "workspace": args[0]},
201
+ "list": lambda: {"action": "ListWorkspaces"},
202
+ "inject": lambda: {"action": "Inject", "workspace": args[0],
203
+ "text": " ".join(args[1:]),
204
+ "from": __import__("os").environ.get("ATERM_WORKSPACE_NAME")},
205
+ "status": lambda: {"action": "WorkspaceStatus", "workspace": args[0]},
206
+ "focus": lambda: {"action": "FocusWorkspace", "workspace": args[0]},
207
+ "create": lambda: {"action": "CreateWorkspace", "name": args[0], "cli": args[1], "cwd": args[2]},
208
+ "kill": lambda: {"action": "CloseWorkspace", "workspace": args[0]},
209
+ "restart": lambda: {"action": "RestartWorkspace", "workspace": args[0]},
210
+ "restart-all": lambda: {"action": "RestartAllWorkspaces"},
52
211
  }
53
212
 
54
213
  payload = json.dumps(payloads[action]()) + "\n"
@@ -78,7 +237,7 @@ try:
78
237
  else:
79
238
  sessions = resp.get("data", [])
80
239
  if not sessions:
81
- print("(no workspaces)")
240
+ print("(워크스페이스 없음)" if lang == "ko" else "(no workspaces)")
82
241
  else:
83
242
  import os.path
84
243
  rows = []
@@ -88,7 +247,7 @@ try:
88
247
  cwd = s.get("cwd", "")
89
248
  terminal = s.get("terminal", "aterm")
90
249
  rows.append((name, cli, cwd, terminal))
91
- cols = ["NAME", "CLI", "CWD", "TERMINAL"]
250
+ cols = ["이름", "CLI", "경로", "터미널"] if lang == "ko" else ["NAME", "CLI", "CWD", "TERMINAL"]
92
251
  widths = [max(len(c), *(len(r[i]) for r in rows)) for i, c in enumerate(cols)]
93
252
  fmt = " ".join(f"{{:<{w}}}" for w in widths)
94
253
  print(fmt.format(*cols))
@@ -159,6 +318,7 @@ print(json.dumps({"sessions": sessions}))
159
318
  import json, sys, os, subprocess, os.path
160
319
 
161
320
  sessions = []
321
+ lang = os.environ.get("ATERM_UI_LANG", "en")
162
322
 
163
323
  # 1. aterm internal sessions
164
324
  sock = os.environ.get("ATERM_IPC_SOCKET", "")
@@ -199,9 +359,9 @@ try:
199
359
  except (FileNotFoundError, subprocess.TimeoutExpired, json.JSONDecodeError): pass
200
360
 
201
361
  if not sessions:
202
- print("(no reachable sessions)")
362
+ print("(도달 가능한 세션 없음)" if lang == "ko" else "(no reachable sessions)")
203
363
  else:
204
- cols = ["NAME", "CLI", "CWD", "TERMINAL"]
364
+ cols = ["이름", "CLI", "경로", "터미널"] if lang == "ko" else ["NAME", "CLI", "CWD", "TERMINAL"]
205
365
  widths = [max(len(c), *(len(r[i]) for r in sessions)) for i, c in enumerate(cols)]
206
366
  fmt = " ".join(f"{{:<{w}}}" for w in widths)
207
367
  print(fmt.format(*cols))
@@ -210,7 +370,10 @@ else:
210
370
  print(fmt.format(*r))
211
371
  if all(r[3] == "aterm" for r in sessions):
212
372
  print()
213
- print("💡 All sessions are aterm-internal. Install telepty for cross-terminal: already included!")
373
+ if lang == "ko":
374
+ print("💡 현재 도달 가능한 세션은 모두 aterm 내부 세션입니다. 다른 터미널과 연결하려면 telepty를 사용하세요: 이미 포함되어 있습니다.")
375
+ else:
376
+ print("💡 All reachable sessions are internal to aterm. Use telepty for cross-terminal access: it is already included.")
214
377
  '
215
378
  fi ;;
216
379
  inject)
@@ -229,6 +392,32 @@ else:
229
392
  [[ -z "$ATERM_IPC_SOCKET" ]] && { echo '{"error":"focus requires aterm (ATERM_IPC_SOCKET not set)"}' >&2; exit 1; }
230
393
  [[ $# -lt 2 ]] && { echo '{"error":"usage: aterm focus <workspace>"}' >&2; exit 1; }
231
394
  aterm_ipc focus "$2" ;;
395
+ create)
396
+ [[ -z "$ATERM_IPC_SOCKET" ]] && { echo '{"error":"create requires aterm (ATERM_IPC_SOCKET not set)"}' >&2; exit 1; }
397
+ # Parse: aterm create <name> --cli <cli> --cwd <path>
398
+ shift
399
+ _name="${1:-}"; shift || true
400
+ _cli=""; _cwd=""
401
+ while [[ $# -gt 0 ]]; do
402
+ case "$1" in
403
+ --cli) _cli="$2"; shift 2 ;;
404
+ --cwd) _cwd="$2"; shift 2 ;;
405
+ *) shift ;;
406
+ esac
407
+ done
408
+ [[ -z "$_name" || -z "$_cli" || -z "$_cwd" ]] && { echo '{"error":"usage: aterm create <name> --cli <cli> --cwd <path>"}' >&2; exit 1; }
409
+ aterm_ipc create "$_name" "$_cli" "$_cwd" ;;
410
+ restart)
411
+ [[ -z "$ATERM_IPC_SOCKET" ]] && { echo '{"error":"restart requires aterm (ATERM_IPC_SOCKET not set)"}' >&2; exit 1; }
412
+ [[ $# -lt 2 ]] && { echo '{"error":"usage: aterm restart <workspace>"}' >&2; exit 1; }
413
+ aterm_ipc restart "$2" ;;
414
+ kill)
415
+ [[ -z "$ATERM_IPC_SOCKET" ]] && { echo '{"error":"kill requires aterm (ATERM_IPC_SOCKET not set)"}' >&2; exit 1; }
416
+ [[ $# -lt 2 ]] && { echo '{"error":"usage: aterm kill <workspace>"}' >&2; exit 1; }
417
+ aterm_ipc kill "$2" ;;
418
+ restart-all)
419
+ [[ -z "$ATERM_IPC_SOCKET" ]] && { echo '{"error":"restart-all requires aterm (ATERM_IPC_SOCKET not set)"}' >&2; exit 1; }
420
+ aterm_ipc restart-all ;;
232
421
  tasks)
233
422
  ensure_tasks_file
234
423
  case "${2:-list}" in
@@ -311,64 +500,254 @@ print(json.dumps({"status":"ok","type": ltype}))
311
500
  ' "$(lessons_file)" "${@:3}" ;;
312
501
  *) echo '{"error":"usage: aterm lessons [list|add]"}' >&2; exit 1 ;;
313
502
  esac ;;
314
- help|--help|-h)
315
- cat <<'HELP'
316
- # aterm — AI-native terminal session communication
503
+ dispatch)
504
+ [[ -z "$ATERM_IPC_SOCKET" ]] && { echo '{"error":"dispatch requires aterm (ATERM_IPC_SOCKET not set)"}' >&2; exit 1; }
505
+ python3 -c '
506
+ import socket
507
+ import subprocess
508
+ import json
509
+ import sys
510
+ import time
511
+ import os
317
512
 
318
- ## Commands
513
+ # ── IPC helper ──────────────────────────────────────────────
319
514
 
320
- | Command | Description |
321
- |---------|-------------|
322
- | `aterm list` | List workspaces (NAME, CLI, CWD, TERMINAL) |
323
- | `aterm inject <workspace> <text>` | Send text to a workspace |
324
- | `aterm status` | Show bundled ecosystem health |
325
- | `aterm status <workspace>` | Check workspace status |
326
- | `aterm focus <workspace>` | Focus a workspace tab |
327
- | `aterm tasks` | List pending tasks |
328
- | `aterm tasks add '<description>'` | Add a new task |
329
- | `aterm tasks done <id>` | Mark task as completed |
330
- | `aterm lessons` | List recorded lessons |
331
- | `aterm lessons add '<lesson>'` | Add a lesson (invariant) |
332
- | `aterm lessons add '<lesson>' --type failed` | Add a failed pattern |
333
- | `aterm help` | Show this help |
515
+ def ipc_send(sock_path, payload):
516
+ """Send JSON to aterm IPC socket and return parsed response."""
517
+ s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
518
+ try:
519
+ s.settimeout(10)
520
+ s.connect(sock_path)
521
+ s.sendall((json.dumps(payload) + "\n").encode())
522
+ s.shutdown(socket.SHUT_WR)
523
+ data = b""
524
+ while True:
525
+ chunk = s.recv(4096)
526
+ if not chunk:
527
+ break
528
+ data += chunk
529
+ if b"\n" in data:
530
+ break
531
+ return json.loads(data.decode().strip().split("\n")[0])
532
+ finally:
533
+ s.close()
334
534
 
335
- ## Ecosystem
535
+ def ipc_create(sock_path, name, cli, cwd):
536
+ return ipc_send(sock_path, {
537
+ "action": "CreateWorkspace", "name": name, "cli": cli, "cwd": cwd
538
+ })
336
539
 
337
- brain (AI profile management) — already installed
338
- telepty (cross-terminal communication) — already installed
339
- deliberation (multi-AI discussions) already installed
340
- devkit (workspace bootstrapping) — already installed
540
+ def ipc_inject(sock_path, workspace, text):
541
+ return ipc_send(sock_path, {
542
+ "action": "Inject", "workspace": workspace, "text": text,
543
+ "from": os.environ.get("ATERM_WORKSPACE_NAME")
544
+ })
341
545
 
342
- ## Natural Language → Command Mapping
343
-
344
- ### Internal sessions (same aterm, $ATERM_IPC_SOCKET set)
345
- | Natural language | Command |
346
- |-----------------|---------|
347
- | 'list sessions' / '세션 목록 보여줘' | `aterm list` |
348
- | 'send build to ghostty' / 'ghostty에 빌드 실행해줘' | `aterm inject ghostty 'make build'` |
349
- | 'check cmux status' / 'cmux 세션 상태 확인해줘' | `aterm status cmux` |
350
- | 'show tasks' / '태스크 목록 보여줘' | `aterm tasks` |
351
- | 'add task: implement API' / '태스크 추가해줘: API 구현' | `aterm tasks add 'API 구현'` |
352
- | 'task 42 done' / '태스크 42 완료' | `aterm tasks done 42` |
353
- | 'show lessons' / '레슨 보여줘' | `aterm lessons` |
354
- | 'add lesson: this pattern failed' / '레슨 추가' | `aterm lessons add 'this pattern failed'` |
355
-
356
- ### External sessions (other terminal/machine, $ATERM_IPC_SOCKET unset)
357
- | Natural language | Command |
358
- |-----------------|---------|
359
- | 'list external sessions' / '외부 세션 목록' | `telepty list` |
360
- | 'message deliberation session' | `telepty inject aigentry-deliberation-claude 'message'` |
546
+ def ipc_status(sock_path, workspace):
547
+ return ipc_send(sock_path, {
548
+ "action": "WorkspaceStatus", "workspace": workspace
549
+ })
361
550
 
362
- ## Session Detection Priority
363
- 1. `aterm list` — internal sessions (project names + terminal=aterm)
364
- 2. `telepty list` — external sessions (project names + terminal type)
365
- 3. NEVER use `ps aux | grep` as primary (no project names, no terminal info)
551
+ def ipc_kill(sock_path, workspace):
552
+ return ipc_send(sock_path, {
553
+ "action": "CloseWorkspace", "workspace": workspace
554
+ })
366
555
 
367
- ## Detection Rules
368
- - `$ATERM_IPC_SOCKET` set → inside aterm. Use `aterm` commands.
369
- - `$ATERM_IPC_SOCKET` unset → outside aterm. Use `telepty` commands.
370
- - `aterm` falls back to `telepty` automatically for list/inject/status.
371
- HELP
556
+ # ── Task breakdown ──────────────────────────────────────────
557
+
558
+ def breakdown_task(description):
559
+ """Try aigentry-devkit breakdown; fall back to single subtask."""
560
+ try:
561
+ result = subprocess.run(
562
+ ["aigentry-devkit", "breakdown", description],
563
+ capture_output=True, text=True, timeout=30
564
+ )
565
+ if result.returncode == 0 and result.stdout.strip():
566
+ data = json.loads(result.stdout)
567
+ subtasks = data if isinstance(data, list) else data.get("subtasks", [])
568
+ if subtasks:
569
+ return subtasks
570
+ except (FileNotFoundError, subprocess.TimeoutExpired, json.JSONDecodeError, Exception):
571
+ pass
572
+ return [{"description": description}]
573
+
574
+ # ── CLI auto-selection ──────────────────────────────────────
575
+
576
+ def select_cli(description):
577
+ """Pick CLI based on keywords in the subtask description."""
578
+ desc_lower = description.lower()
579
+ codex_keywords = ["implement", "code", "build", "write", "fix bug", "create file", "add function"]
580
+ claude_keywords = ["architect", "debug", "analyze", "design", "refactor", "review"]
581
+ gemini_keywords = ["research", "document", "search", "summarize", "find information"]
582
+ for kw in codex_keywords:
583
+ if kw in desc_lower:
584
+ return "codex"
585
+ for kw in claude_keywords:
586
+ if kw in desc_lower:
587
+ return "claude"
588
+ for kw in gemini_keywords:
589
+ if kw in desc_lower:
590
+ return "gemini"
591
+ return "claude"
592
+
593
+ # ── Poll for completion ─────────────────────────────────────
594
+
595
+ def poll_session(sock_path, name, timeout=300, interval=10):
596
+ """Poll workspace status until idle or timeout."""
597
+ elapsed = 0
598
+ while elapsed < timeout:
599
+ time.sleep(interval)
600
+ elapsed += interval
601
+ try:
602
+ resp = ipc_status(sock_path, name)
603
+ status_data = resp.get("data", resp)
604
+ state = status_data.get("state", "")
605
+ if state == "idle":
606
+ return "complete"
607
+ except Exception:
608
+ pass
609
+ return "timeout"
610
+
611
+ # ── Main ────────────────────────────────────────────────────
612
+
613
+ def main():
614
+ if len(sys.argv) < 2:
615
+ print(json.dumps({"error": "usage: aterm dispatch <task-id> | --plan <description>"}), file=sys.stderr)
616
+ sys.exit(1)
617
+
618
+ sock_path = sys.argv[1]
619
+ tasks_file = sys.argv[2]
620
+ args = sys.argv[3:]
621
+
622
+ if not args:
623
+ print(json.dumps({"error": "usage: aterm dispatch <task-id> | --plan <description>"}), file=sys.stderr)
624
+ sys.exit(1)
625
+
626
+ # Parse: --plan or task-id
627
+ task_id = None
628
+ description = None
629
+
630
+ if args[0] == "--plan":
631
+ if len(args) < 2:
632
+ print(json.dumps({"error": "usage: aterm dispatch --plan <description>"}), file=sys.stderr)
633
+ sys.exit(1)
634
+ description = " ".join(args[1:])
635
+ task_id = "plan"
636
+ else:
637
+ task_id = args[0]
638
+ try:
639
+ tid = int(task_id)
640
+ except ValueError:
641
+ print(json.dumps({"error": f"invalid task-id: {task_id}"}), file=sys.stderr)
642
+ sys.exit(1)
643
+ # Read from task-queue.json
644
+ try:
645
+ with open(tasks_file) as f:
646
+ data = json.load(f)
647
+ found = None
648
+ for t in data.get("tasks", []):
649
+ if isinstance(t, dict) and t.get("id") == tid:
650
+ found = t
651
+ break
652
+ if not found:
653
+ print(json.dumps({"error": f"task {tid} not found in {tasks_file}"}), file=sys.stderr)
654
+ sys.exit(1)
655
+ description = found.get("description", "")
656
+ except (FileNotFoundError, json.JSONDecodeError) as e:
657
+ print(json.dumps({"error": f"cannot read tasks file: {e}"}), file=sys.stderr)
658
+ sys.exit(1)
659
+
660
+ # Breakdown
661
+ subtasks = breakdown_task(description)
662
+
663
+ # Subagent vs subsession judgment
664
+ if len(subtasks) == 1:
665
+ desc = subtasks[0].get("description", subtasks[0]) if isinstance(subtasks[0], dict) else str(subtasks[0])
666
+ # Check if description mentions a single file
667
+ single_file_hints = [".rs", ".ts", ".js", ".py", ".swift", ".go", ".java", ".c", ".cpp", ".h", ".md"]
668
+ if any(hint in desc for hint in single_file_hints):
669
+ print(json.dumps({
670
+ "recommendation": "subagent",
671
+ "reason": "single file, simple task",
672
+ "task_id": task_id,
673
+ "description": desc
674
+ }))
675
+ sys.exit(0)
676
+
677
+ # Create sub-sessions, inject, poll, collect, kill
678
+ cwd = os.getcwd()
679
+ sessions_created = []
680
+ reports = []
681
+
682
+ for i, st in enumerate(subtasks):
683
+ desc = st.get("description", st) if isinstance(st, dict) else str(st)
684
+ cli = select_cli(desc)
685
+ name = f"dispatch-{task_id}-sub{i}"
686
+
687
+ # Create session
688
+ try:
689
+ ipc_create(sock_path, name, cli, cwd)
690
+ sessions_created.append(name)
691
+ except Exception as e:
692
+ reports.append({"name": name, "status": "create_failed", "cli": cli, "error": str(e)})
693
+ continue
694
+
695
+ # Wait for session to start
696
+ time.sleep(2)
697
+
698
+ # Inject task description
699
+ try:
700
+ ipc_inject(sock_path, name, desc)
701
+ except Exception as e:
702
+ reports.append({"name": name, "status": "inject_failed", "cli": cli, "error": str(e)})
703
+ continue
704
+
705
+ # Poll all sessions for completion
706
+ for name in sessions_created:
707
+ # Skip sessions that already failed
708
+ if any(r["name"] == name for r in reports):
709
+ continue
710
+ cli = "unknown"
711
+ for i, st in enumerate(subtasks):
712
+ if f"dispatch-{task_id}-sub{i}" == name:
713
+ desc = st.get("description", st) if isinstance(st, dict) else str(st)
714
+ cli = select_cli(desc)
715
+ break
716
+ status = poll_session(sock_path, name)
717
+ reports.append({"name": name, "status": status, "cli": cli})
718
+
719
+ # Kill all sub-sessions
720
+ for name in sessions_created:
721
+ try:
722
+ ipc_kill(sock_path, name)
723
+ except Exception:
724
+ pass
725
+
726
+ # Determine overall status
727
+ statuses = [r["status"] for r in reports]
728
+ if all(s == "complete" for s in statuses):
729
+ overall = "all_complete"
730
+ elif all(s == "timeout" for s in statuses):
731
+ overall = "all_timeout"
732
+ elif any(s == "complete" for s in statuses):
733
+ overall = "partial_complete"
734
+ else:
735
+ overall = "failed"
736
+
737
+ result = {
738
+ "task_id": task_id if task_id == "plan" else int(task_id),
739
+ "subtasks": len(subtasks),
740
+ "sessions_created": sessions_created,
741
+ "status": overall,
742
+ "reports": reports
743
+ }
744
+ print(json.dumps(result, indent=2))
745
+
746
+ main()
747
+ ' "$ATERM_IPC_SOCKET" "$(tasks_file)" "${@:2}"
748
+ ;;
749
+ help|--help|-h)
750
+ show_help
372
751
  ;;
373
752
  *) echo "{\"error\":\"unknown command: $1\"}" >&2; exit 1 ;;
374
753
  esac
@@ -6,7 +6,7 @@
6
6
  <dict>
7
7
  <key>Resources/bin/aterm</key>
8
8
  <data>
9
- 1HuLkmDie3Huck1M3zk1y2k9nHk=
9
+ B0IY8YjfGFvwfuMtAo6dKNuwNqY=
10
10
  </data>
11
11
  </dict>
12
12
  <key>files2</key>
@@ -15,16 +15,16 @@
15
15
  <dict>
16
16
  <key>cdhash</key>
17
17
  <data>
18
- 9WsGAC35qT+7mJPrbcRgCAnPUmU=
18
+ 5x/w06jX/30THayLISHAxO7Ujv8=
19
19
  </data>
20
20
  <key>requirement</key>
21
- <string>cdhash H"f56b06002df9a93fbb9893eb6dc4600809cf5265"</string>
21
+ <string>cdhash H"e71ff0d3a8d7ff7d131dac8b2121c0c4eed48eff"</string>
22
22
  </dict>
23
23
  <key>Resources/bin/aterm</key>
24
24
  <dict>
25
25
  <key>hash2</key>
26
26
  <data>
27
- 2w1QxQ4iZe+lQkanS659+19iwyQvKcZGja/KKimSqcw=
27
+ 2WJQSujew/jyCHX49GN82hlmuOg+XHTuYjlUxkHpK0Q=
28
28
  </data>
29
29
  </dict>
30
30
  </dict>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dmsdc-ai/aterm-darwin-arm64",
3
- "version": "0.1.45",
3
+ "version": "0.1.47",
4
4
  "description": "darwin-arm64 native bundle for @dmsdc-ai/aterm",
5
5
  "type": "module",
6
6
  "files": [