easy-coding-harness 0.3.3 → 0.4.0

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.
package/CHANGELOG.md CHANGED
@@ -6,6 +6,19 @@
6
6
  - `y`:常规功能升级
7
7
  - `z`:日常 bug 修复
8
8
 
9
+ ## 0.4.0
10
+
11
+ - 升级跨 agent 交接模型:交接记录只保存交接前 agent、阶段、摘要和时间,不再要求也不保存下一任 agent。
12
+ - 新增 `handoff-task` / `claim-task` 状态 API,交接方可写入 handoff 并释放当前 session,新 agent 可显式 claim 任务并读取最新交接摘要。
13
+ - `ec-task-management` 升级为任务面板:列出未完成任务并标注继续/接手,接手任务展示上一任 agent。
14
+ - `ec-workflow` 路由调整:有当前任务指针时优先继续;无指针时按提示词匹配未完成任务,未命中或无提示词时展示可继续/接手任务列表。
15
+
16
+ ## 0.3.4
17
+
18
+ - 修复 Claude Code 中任务已进入 ANALYSIS 但回复没有状态栏的问题:Claude 的 `UserPromptSubmit` 现在会先运行幂等的 `session-start.py`,再运行 `inject-workflow-state.py`,确保每轮提示词都能拿到最新 `status_context`。
19
+ - 保留 Claude 原有 `SessionStart` 启动初始化,同时对齐 Codex/Qoder 的每轮状态注入方式,避免原生会话事件未进入模型上下文时状态栏缺失。
20
+ - 存量 Claude 项目需要执行 `easy-coding upgrade` 刷新 `.claude/settings.json` 后生效;Codex/Qoder 行为不变。
21
+
9
22
  ## 0.3.3
10
23
 
11
24
  - 修复同一轮会话内状态栏仍显示旧状态的问题:`easy_coding_state.py` 的所有写状态命令现在都会在写入后立即回读 session/task,并返回最新 `status_line` 和 `status_context`。
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "easy-coding-harness",
3
- "version": "0.3.3",
3
+ "version": "0.4.0",
4
4
  "description": "CLI scaffold for installing Easy Coding harness files into agent-native directories.",
5
5
  "repository": {
6
6
  "type": "git",
@@ -13,6 +13,16 @@
13
13
  }
14
14
  ],
15
15
  "UserPromptSubmit": [
16
+ {
17
+ "matcher": "",
18
+ "hooks": [
19
+ {
20
+ "type": "command",
21
+ "command": "{{PYTHON_CMD}} .claude/hooks/session-start.py",
22
+ "timeout": 15000
23
+ }
24
+ ]
25
+ },
16
26
  {
17
27
  "matcher": "",
18
28
  "hooks": [
@@ -82,3 +82,8 @@ demand.
82
82
  continues. All platform-agnostic artifacts (dev-spec, execution.jsonl, task.json, memory)
83
83
  make cross-agent handoff lossless. `task.json.last_agent` records the last owner so a new
84
84
  agent knows a task was handed off rather than self-interrupted.
85
+
86
+ Handoff is target-less. The leaving agent writes a `handoff` record with `from`, `stage`,
87
+ `summary`, and `timestamp`, then releases its session pointer. It does not know or record the
88
+ next agent. The receiving agent explicitly claims a task through the state API; that claim sets
89
+ the new session pointer and updates `task.json.last_agent`.
@@ -46,11 +46,14 @@ Python runtime files are shared verbatim across platforms (only the JSON wrapper
46
46
  Wrapper differences:
47
47
 
48
48
  - **Claude Code**: full event set — `SessionStart`, `UserPromptSubmit`, `PreToolUse(Agent)`.
49
+ `session-start.py` also runs on `UserPromptSubmit` before `inject-workflow-state.py`, so
50
+ every prompt receives a fresh status context even if the native session event is not surfaced
51
+ to the model turn.
49
52
  - **Codex**: no `SessionStart` and no Agent tool. `session-start.py` and
50
53
  `inject-workflow-state.py` both hang off `UserPromptSubmit`; `inject-subagent-context.py`
51
54
  is skipped. Codex hooks also require user-level enablement (`[features] hooks = true`).
52
55
  - **Qoder**: like Claude Code (has Agent tool + Stop) but uses `UserPromptSubmit` for state
53
56
  injection. The `.qoder/settings.json` wrapper nests an extra `hooks` array.
54
57
 
55
- `session-start.py` is designed to be idempotent precisely because Codex/Qoder fire it on
56
- `UserPromptSubmit` rather than a real session-start event — repeated calls are safe.
58
+ `session-start.py` is designed to be idempotent precisely because Claude/Codex/Qoder can fire it
59
+ on `UserPromptSubmit` rather than only a real session-start event — repeated calls are safe.
@@ -1,23 +1,56 @@
1
1
  ---
2
2
  name: ec-task-management
3
- description: Read-only task panel create and list Easy Coding tasks. Use when the user runs {{skill_trigger}}ec-task-management or asks to see what tasks exist or to create a task. Does NOT perform workflow recovery (that is ec-workflow) or closure (that is ec-task-close).
3
+ description: Task panel for Easy Coding tasks. Use when the user runs {{skill_trigger}}ec-task-management, asks to see tasks, create a task, continue a task, or take over a task handed off by another agent.
4
4
  ---
5
5
 
6
6
  # ec-task-management — the task panel
7
7
 
8
- A focused information panel: create tasks and list them. Recovery and stage progression
9
- belong to ec-workflow; closure belongs to ec-task-close. This skill does neither.
8
+ A focused task panel: list unfinished tasks, create tasks, and let the user choose one task
9
+ to continue or take over. Stage progression still belongs to ec-workflow; closure belongs
10
+ to ec-task-close.
10
11
 
11
12
  Communicate with the user in the user's language.
12
13
 
13
14
  ## Capabilities
14
15
 
15
- ### List tasks
16
- Call the state API:
17
- `{{PYTHON_CMD}} {{platform_config_dir}}/hooks/easy_coding_state.py list-tasks`.
18
- Show each task's id, title, status, created_at, and whether it is active. This is read-only.
16
+ ### List unfinished tasks
17
+
18
+ Call the state API with the current agent id:
19
+ `{{PYTHON_CMD}} {{platform_config_dir}}/hooks/easy_coding_state.py list-tasks --agent <agent-id>`.
20
+
21
+ Show only tasks whose `active` is true. For each task show:
22
+ - id
23
+ - title
24
+ - status
25
+ - created_at
26
+ - action label:
27
+ - `continue` when `action == "continue"`
28
+ - `take over` when `action == "takeover"`
29
+ - previous agent when `previous_agent` is present
30
+ - latest handoff summary when `latest_handoff.summary` is present
31
+
32
+ Use wording in the user's language. The important distinction is:
33
+ - Continue: the current agent was already the last agent.
34
+ - Take over: another agent was the last agent; show that previous agent.
35
+
36
+ ### Continue or take over a selected task
37
+
38
+ When the user chooses a listed task, call:
39
+ `{{PYTHON_CMD}} {{platform_config_dir}}/hooks/easy_coding_state.py claim-task --session-file <P> --task-id <task-id> --agent <agent-id>`.
40
+
41
+ Use the returned `status_context` as the authoritative status source for the rest of the
42
+ current turn. Then report:
43
+ - whether this was `continue` or `takeover`
44
+ - the previous agent if returned
45
+ - the latest handoff summary if returned
46
+ - the current stage
47
+
48
+ After claiming, hand control to ec-workflow semantics: read the task metadata and latest
49
+ execution records, then resume from the current stage. Do not advance stages from this skill
50
+ unless ec-workflow is explicitly invoked or the agent environment routes into it.
19
51
 
20
52
  ### Create a task
53
+
21
54
  Create through the state API, never by hand-editing `task.json` or session files:
22
55
  `{{PYTHON_CMD}} {{platform_config_dir}}/hooks/easy_coding_state.py create-task --session-file <P> --task-id <MM-DD-short-name> --type <feature|bugfix|refactor|perf> --title "<one-line summary>" --agent <agent-id> --no-set-current`.
23
56
 
@@ -31,6 +64,7 @@ the current turn instead of older hook-injected status text.
31
64
 
32
65
  ## Boundaries
33
66
 
34
- - No workflow recovery point the user to ec-workflow to resume or progress a task.
35
- - No closing/cancelling that is ec-task-close.
36
- - Do not advance stages or run stage skills from here.
67
+ - Do not close or cancel tasks that is ec-task-close.
68
+ - Do not advance stages directly from this skill.
69
+ - Do not ask who the next agent will be. A takeover is decided by the agent that claims the
70
+ task, not by the agent that last handed it off.
@@ -29,33 +29,32 @@ replies are English.
29
29
  Do NOT bulk-read ABSTRACT.md or long memory here; ec-analysis loads them on demand.
30
30
  3. **State check + Intent routing.** Read the hook-injected breadcrumbs (`[current-task:X]`,
31
31
  `[workflow-state:Y]`, `[easy-coding:session-file:P]`) to determine the active task,
32
- stage, and session file, then decide based on whether the user's message carries a
33
- task-related prompt beyond the bare skill trigger.
34
-
35
- **No prompt (bare trigger):**
36
- - `current_task` set with an active stage → resume that stage (see Resume and handoff).
37
- - `current_task` null scan `.easy-coding/tasks/` for tasks whose status is not in
38
- {COMPLETE, CLOSED}. Found → list them, let the user pick one to resume or start new.
39
- None ready for a new task.
40
-
41
- **With prompt intent routing:**
42
- 1. Collect all non-terminal tasks in `.easy-coding/tasks/`: read each task's folder name,
43
- `task.json` fields (`title`, `type`, `status`). If a task passed ANALYSIS, also read
44
- its dev-spec title line as extra matching signal.
45
- 2. Match the user's prompt against these identifiers (semantic match — the user saying
46
- "继续做搜索" should match a task titled "添加搜索功能").
47
- 3. Route:
48
- - Prompt matches `current_task` resume with the prompt as additional context (the
49
- user may be providing supplementary info, a revision request, or an answer to a
50
- previous question).
51
- - Prompt matches a different non-terminal task ask the user: "Switch to «{matched
52
- task title}»? Current task «{current task title}» will be suspended at {stage}."
53
- On confirmation, perform a task switch (see Task switching below).
54
- - Prompt matches no existing task AND `current_task` is set ask the user: "Start a
55
- new task? Current task «{current task title}» will be suspended at {stage}."
56
- On confirmation, create the new task (step 4).
57
- - Prompt matches no existing task AND `current_task` is null create the new task
58
- directly (step 4).
32
+ stage, and session file. Then call the task list API with the current agent id:
33
+ `{{PYTHON_CMD}} {{platform_config_dir}}/hooks/easy_coding_state.py list-tasks --agent <agent-id>`.
34
+ Use only non-terminal tasks (`active == true`) for routing.
35
+
36
+ **Current task pointer exists:**
37
+ - The current task has priority. Resume it by default, even if the user only runs the bare
38
+ skill trigger.
39
+ - If the prompt clearly matches the current task, resume it with the prompt as additional
40
+ context.
41
+ - If the prompt clearly matches a different unfinished task, ask whether to switch to that
42
+ task. Show whether it is `continue` or `takeover`; for takeover show `previous_agent`.
43
+ On confirmation, claim the matched task (see Task switching).
44
+ - If the prompt clearly describes unrelated new work, ask whether to create a new task and
45
+ suspend the current one at its persisted stage.
46
+
47
+ **No current task pointer:**
48
+ - If the prompt matches exactly one unfinished task, show that task and ask whether to
49
+ continue/take it over. For takeover show the previous agent and latest handoff summary.
50
+ On confirmation, claim it (see Task switching).
51
+ - If the prompt matches multiple unfinished tasks, list the matches with `continue` /
52
+ `takeover` labels and ask the user to choose one.
53
+ - If the prompt matches none, or there is no prompt beyond the bare skill trigger, list all
54
+ unfinished tasks with `continue` / `takeover` labels. For takeover entries, show the
55
+ previous agent. Let the user choose one to claim, or choose to start a new task.
56
+ - If there are no unfinished tasks, create a new task only when the user supplied a real
57
+ task prompt; otherwise report that the harness is ready for a new task.
59
58
 
60
59
  4. **New task.** When creating a task (from step 3), create
61
60
  the task through the state API, which creates `task.json`, sets `status:"INIT"`, writes
@@ -98,10 +97,11 @@ any stage --[user abort via ec-task-close]--> CLOSED
98
97
 
99
98
  When the user confirms switching from task A to task B:
100
99
  1. Task A's status is already persisted in its `task.json` — nothing extra to save.
101
- 2. Set the current task through the state API:
102
- `{{PYTHON_CMD}} {{platform_config_dir}}/hooks/easy_coding_state.py set-current --session-file <P> --task-id <task-b-id> --agent <agent-id>`.
100
+ 2. Claim the selected task through the state API:
101
+ `{{PYTHON_CMD}} {{platform_config_dir}}/hooks/easy_coding_state.py claim-task --session-file <P> --task-id <task-b-id> --agent <agent-id>`.
103
102
  3. Use the returned `status_context` as the current status source, then read task B's
104
- `task.json` to determine its current stage.
103
+ `task.json` to determine its current stage. If the result says `action:"takeover"`, tell
104
+ the user which previous agent owned the task.
105
105
  4. Resume task B's stage via the appropriate stage skill.
106
106
 
107
107
  No data is lost — task A's dev-spec, execution.jsonl, and test-strategy.md stay intact on
@@ -180,12 +180,12 @@ Hook breadcrumbs you may receive: `[workflow-state:X]`, `[current-task:Y]`,
180
180
  `[easy-coding:session-file:P]`, `[easy-coding:handoff-from:Z]`,
181
181
  `[easy-coding:init-required]`.
182
182
 
183
- Resuming an active task (whether from session restart, handoff, or task switch):
183
+ Resuming an active task (whether from session restart, claim, handoff, or task switch):
184
184
  1. Read `task.json` and the dev-spec sections relevant to the current stage.
185
185
  2. Read the tail of `execution.jsonl` — the latest `plan` / `result` / `verify` / `handoff`
186
186
  records tell you exactly where work stopped.
187
- 3. If `last_agent` differs from the current agent this is a cross-agent handoff: read the
188
- latest `handoff` record first for the fast summary, then set `last_agent` to yourself.
187
+ 3. If the task was claimed from another agent, read the latest `handoff` record first for the
188
+ fast summary and tell the user which previous agent handed it off.
189
189
  4. Tell the user what is being resumed and from which stage, then continue.
190
190
 
191
191
  After a task switch, the same resume flow applies — the only difference is that `current_task`
@@ -193,13 +193,19 @@ was just changed by the switching procedure rather than being loaded from a prio
193
193
 
194
194
  Offering handoff — at WAITING_CONFIRM, after presenting the plan, offer exactly:
195
195
  1. Start implementation
196
- 2. Hand off to another agent
196
+ 2. Write handoff and stop
197
197
  3. Revise the plan
198
- On option 2: append a `handoff` record to `execution.jsonl` —
199
- `{"type":"handoff","from":"<agent>","stage":"<stage>","summary":"<dense context: plan shape, key decisions, user emphases>","timestamp":"<ISO>"}` —
200
- update the task's `last_agent` in task.json, then tell the user to open the target agent and run ec-workflow there.
201
- Handoff is also legal at any other stage boundary on user request. The harness never
202
- switches agents by itself.
198
+ On option 2, call:
199
+ `{{PYTHON_CMD}} {{platform_config_dir}}/hooks/easy_coding_state.py handoff-task --session-file <P> --agent <agent-id> --summary "<dense context: plan shape, key decisions, user emphases>"`.
200
+
201
+ The handoff record is target-less:
202
+ `{"type":"handoff","from":"<agent>","stage":"<stage>","summary":"<dense context>","timestamp":"<ISO>"}`.
203
+ It records who handed the task off, not who will take it next. Do not ask the user to name
204
+ the next agent, and do not invent or store a next-agent field. After writing handoff, stop
205
+ owning the task; another agent can use ec-task-management or ec-workflow to claim it.
206
+
207
+ Handoff is also legal at any other stage boundary on user request. The harness never switches
208
+ agents by itself; the next agent claims the task explicitly.
203
209
 
204
210
  ## Status line
205
211
 
@@ -242,6 +242,38 @@ def write_task(root: Path, task_id: str, task: dict) -> None:
242
242
  write_json(task_json_path(root, task_id), task)
243
243
 
244
244
 
245
+ def execution_log_path(root: Path, task_id: str) -> Path:
246
+ assert_safe_task_id(task_id)
247
+ return root / ".easy-coding" / "tasks" / task_id / "execution.jsonl"
248
+
249
+
250
+ def append_execution_record(root: Path, task_id: str, record: dict) -> None:
251
+ path = execution_log_path(root, task_id)
252
+ path.parent.mkdir(parents=True, exist_ok=True)
253
+ with path.open("a", encoding="utf-8") as handle:
254
+ handle.write(json.dumps(record, ensure_ascii=False) + "\n")
255
+
256
+
257
+ def latest_handoff_record(root: Path, task_id: str) -> dict | None:
258
+ path = execution_log_path(root, task_id)
259
+ if not path.exists():
260
+ return None
261
+ latest: dict | None = None
262
+ try:
263
+ for line in path.read_text(encoding="utf-8").splitlines():
264
+ if not line.strip():
265
+ continue
266
+ try:
267
+ record = json.loads(line)
268
+ except json.JSONDecodeError:
269
+ continue
270
+ if isinstance(record, dict) and record.get("type") == "handoff":
271
+ latest = record
272
+ except OSError:
273
+ return None
274
+ return latest
275
+
276
+
245
277
  def assert_safe_task_id(task_id: str) -> None:
246
278
  path = Path(task_id)
247
279
  if not task_id or path.is_absolute() or "/" in task_id or "\\" in task_id or ".." in path.parts:
@@ -432,7 +464,15 @@ def attach_status_context(
432
464
  return enriched
433
465
 
434
466
 
435
- def list_tasks(root: Path) -> list[dict]:
467
+ def task_claim_action(task: dict, agent: str | None) -> str | None:
468
+ status = str(task.get("status") or "PENDING")
469
+ if status in TERMINAL_STATUSES or not agent:
470
+ return None
471
+ last_agent = task.get("last_agent")
472
+ return "continue" if not last_agent or last_agent == agent else "takeover"
473
+
474
+
475
+ def list_tasks(root: Path, agent: str | None = None) -> list[dict]:
436
476
  tasks_dir = root / ".easy-coding" / "tasks"
437
477
  if not tasks_dir.is_dir():
438
478
  return []
@@ -444,6 +484,8 @@ def list_tasks(root: Path) -> list[dict]:
444
484
  if not task:
445
485
  continue
446
486
  status = str(task.get("status") or "PENDING")
487
+ action = task_claim_action(task, agent)
488
+ last_agent = task.get("last_agent")
447
489
  items.append(
448
490
  {
449
491
  "id": entry.name,
@@ -452,7 +494,10 @@ def list_tasks(root: Path) -> list[dict]:
452
494
  "status": status,
453
495
  "active": status not in TERMINAL_STATUSES,
454
496
  "created_at": task.get("created_at"),
455
- "last_agent": task.get("last_agent"),
497
+ "last_agent": last_agent,
498
+ "action": action,
499
+ "previous_agent": last_agent if action == "takeover" else None,
500
+ "latest_handoff": latest_handoff_record(root, entry.name),
456
501
  }
457
502
  )
458
503
  return items
@@ -487,6 +532,77 @@ def clear_current_task(root: Path, agent: str, session_file: str | Path | None =
487
532
  return snapshot_state(root, session_file, session)
488
533
 
489
534
 
535
+ def handoff_task(
536
+ root: Path,
537
+ agent: str,
538
+ summary: str,
539
+ task_id: str | None = None,
540
+ session_file: str | Path | None = None,
541
+ ) -> dict:
542
+ if not summary.strip():
543
+ raise StateError("Handoff summary is required.")
544
+ session = ensure_session(root, session_file)
545
+ resolved_task_id = task_id or session.get("current_task")
546
+ if not resolved_task_id:
547
+ raise StateError("No current task is set.")
548
+ task = load_task(root, str(resolved_task_id))
549
+ if task is None:
550
+ raise StateError(f"Task not found: {resolved_task_id}")
551
+ stage = str(task.get("status") or "PENDING")
552
+ if stage in TERMINAL_STATUSES:
553
+ raise StateError(f"Cannot hand off terminal task: {resolved_task_id}")
554
+
555
+ record = {
556
+ "type": "handoff",
557
+ "from": agent,
558
+ "stage": stage,
559
+ "summary": summary.strip(),
560
+ "timestamp": now_iso(),
561
+ }
562
+ append_execution_record(root, str(resolved_task_id), record)
563
+ task["last_agent"] = agent
564
+ write_task(root, str(resolved_task_id), task)
565
+
566
+ if session.get("current_task") == resolved_task_id:
567
+ clear_session_pointer(session, agent)
568
+ write_session(root, session, session_file)
569
+
570
+ snapshot = snapshot_state(root, session_file, session)
571
+ snapshot["task_id"] = str(resolved_task_id)
572
+ snapshot["handoff"] = record
573
+ snapshot["action"] = "handoff"
574
+ return snapshot
575
+
576
+
577
+ def claim_task(root: Path, task_id: str, agent: str, session_file: str | Path | None = None) -> dict:
578
+ task = load_task(root, task_id)
579
+ if task is None:
580
+ raise StateError(f"Task not found: {task_id}")
581
+ status = str(task.get("status") or "PENDING")
582
+ if status in TERMINAL_STATUSES:
583
+ raise StateError(f"Cannot claim terminal task: {task_id}")
584
+
585
+ previous_agent = task.get("last_agent")
586
+ action = "continue" if not previous_agent or previous_agent == agent else "takeover"
587
+ latest_handoff = latest_handoff_record(root, task_id)
588
+ task["last_agent"] = agent
589
+ write_task(root, task_id, task)
590
+
591
+ session = ensure_session(root, session_file)
592
+ session["current_task"] = task_id
593
+ session["last_seen_task"] = task_id
594
+ session["last_seen_stage"] = status
595
+ session["last_agent"] = agent
596
+ write_session(root, session, session_file)
597
+
598
+ snapshot = snapshot_state(root, session_file, session)
599
+ snapshot["task_id"] = task_id
600
+ snapshot["action"] = action
601
+ snapshot["previous_agent"] = previous_agent
602
+ snapshot["latest_handoff"] = latest_handoff
603
+ return snapshot
604
+
605
+
490
606
  def create_task(
491
607
  root: Path,
492
608
  task_id: str,
@@ -675,7 +791,9 @@ def main() -> int:
675
791
  subcommands = parser.add_subparsers(dest="command")
676
792
 
677
793
  subcommands.add_parser("snapshot", parents=[common])
678
- subcommands.add_parser("list-tasks", parents=[common])
794
+
795
+ list_tasks_parser = subcommands.add_parser("list-tasks", parents=[common])
796
+ list_tasks_parser.add_argument("--agent")
679
797
 
680
798
  create = subcommands.add_parser("create-task", parents=[common])
681
799
  create.add_argument("--task-id", required=True)
@@ -691,6 +809,15 @@ def main() -> int:
691
809
  clear_current = subcommands.add_parser("clear-current", parents=[common])
692
810
  clear_current.add_argument("--agent", required=True)
693
811
 
812
+ handoff = subcommands.add_parser("handoff-task", parents=[common])
813
+ handoff.add_argument("--agent", required=True)
814
+ handoff.add_argument("--summary", required=True)
815
+ handoff.add_argument("--task-id")
816
+
817
+ claim = subcommands.add_parser("claim-task", parents=[common])
818
+ claim.add_argument("--task-id", required=True)
819
+ claim.add_argument("--agent", required=True)
820
+
694
821
  transition = subcommands.add_parser("transition", parents=[common])
695
822
  transition.add_argument("--stage", required=True)
696
823
  transition.add_argument("--agent", required=True)
@@ -716,7 +843,7 @@ def main() -> int:
716
843
  if command == "snapshot":
717
844
  emit(snapshot_state(root, session_file))
718
845
  elif command == "list-tasks":
719
- emit({"tasks": list_tasks(root)})
846
+ emit({"tasks": list_tasks(root, getattr(args, "agent", None))})
720
847
  elif command == "create-task":
721
848
  emit(
722
849
  attach_status_context(
@@ -752,6 +879,24 @@ def main() -> int:
752
879
  session_file,
753
880
  )
754
881
  )
882
+ elif command == "handoff-task":
883
+ emit(
884
+ attach_status_context(
885
+ root,
886
+ handoff_task(root, args.agent, args.summary, args.task_id, session_file),
887
+ args.agent,
888
+ session_file,
889
+ )
890
+ )
891
+ elif command == "claim-task":
892
+ emit(
893
+ attach_status_context(
894
+ root,
895
+ claim_task(root, args.task_id, args.agent, session_file),
896
+ args.agent,
897
+ session_file,
898
+ )
899
+ )
755
900
  elif command == "transition":
756
901
  emit(
757
902
  attach_status_context(