okstra 0.10.0 → 0.11.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/README.kr.md +2 -0
- package/README.md +2 -0
- package/package.json +1 -1
- package/runtime/BUILD.json +2 -2
- package/runtime/agents/SKILL.md +3 -2
- package/runtime/prompts/launch.template.md +26 -0
- package/runtime/python/okstra_ctl/run.py +15 -2
- package/runtime/python/okstra_ctl/session.py +33 -0
- package/runtime/skills/okstra-history/SKILL.md +8 -3
- package/runtime/skills/okstra-report-finder/SKILL.md +8 -3
- package/runtime/skills/okstra-run/SKILL.md +11 -3
- package/runtime/skills/okstra-schedule/SKILL.md +8 -3
- package/runtime/skills/okstra-setup/SKILL.md +8 -2
- package/runtime/skills/okstra-status/SKILL.md +8 -3
- package/runtime/skills/okstra-time-summary/SKILL.md +8 -3
- package/runtime/validators/validate-run.py +22 -0
package/README.kr.md
CHANGED
|
@@ -109,6 +109,8 @@ okstra install # 'npx -y okstra@latest install' 와 동일
|
|
|
109
109
|
|
|
110
110
|
글로벌 설치는 Node CLI 를 PATH 에 등록할 뿐입니다. 런타임(`~/.okstra/`) 과 Claude 스킬(`~/.claude/skills/`) 은 여전히 `okstra install` 이 생성합니다 — `npm i -g` 에 포함되지 않습니다. 이후 업그레이드: `npm i -g okstra@latest && okstra install`. 글로벌 바이너리 제거: `npm uninstall -g okstra` (`~/.okstra/` 는 그대로; 그것까지 지우려면 `okstra uninstall`).
|
|
111
111
|
|
|
112
|
+
**글로벌 설치 시 스킬 동작.** 모든 okstra 스킬은 PATH 에 잡힌 `okstra` 를 자동 감지하여 `npx -y okstra@latest` 대신 우선 사용합니다. 즉 글로벌 설치를 해두면 매 스킬 호출(`okstra-run`, `okstra-status`, `okstra-history`, `okstra-schedule`, `okstra-report-finder`, `okstra-time-summary`, `okstra-setup` Step 2 의 Step 0) 마다 npx 가 패키지 fetch / 버전 체크하던 비용이 사라집니다. 스킬이 본인이 설치한 버전을 그대로 쓰므로 **업그레이드 타이밍은 사용자가 통제** 합니다 — 더 이상 호출마다 `@latest` 가 강제되지 않습니다. 새 릴리스를 받으려면 원하는 시점에 `npm i -g okstra@latest && okstra install` 을 실행하세요. `okstra` 가 PATH 에 없으면 스킬은 자동으로 npx fallback 으로 동작하므로 글로벌 설치가 없는 환경에서도 변경 없이 그대로 동작합니다.
|
|
113
|
+
|
|
112
114
|
### 3.2 프로젝트 등록 (프로젝트당 1회)
|
|
113
115
|
|
|
114
116
|
CLI 에서:
|
package/README.md
CHANGED
|
@@ -108,6 +108,8 @@ okstra install # same as 'npx -y okstra@latest install'
|
|
|
108
108
|
|
|
109
109
|
The global install only registers the Node CLI on your PATH. The runtime (`~/.okstra/`) and the Claude skills (`~/.claude/skills/`) are still provisioned by `okstra install` — they are not part of `npm i -g`. To upgrade later: `npm i -g okstra@latest && okstra install`. To remove the global binary: `npm uninstall -g okstra` (leaves `~/.okstra/` untouched; remove that with `okstra uninstall`).
|
|
110
110
|
|
|
111
|
+
**Skill behaviour with a global install.** All okstra skills auto-detect a PATH-resolved `okstra` and prefer it over `npx -y okstra@latest`. That means a global install removes the per-call npx fetch / version-check from every skill invocation (Step 0 of `okstra-run`, `okstra-status`, `okstra-history`, `okstra-schedule`, `okstra-report-finder`, `okstra-time-summary`, `okstra-setup` Step 2). Since the skill uses your globally installed version directly, *you* control upgrade timing — `@latest` is no longer forced on each call. Run `npm i -g okstra@latest && okstra install` whenever you want to pull a new release. If `okstra` is not on PATH the skill silently falls back to npx, so machines without a global install keep working unchanged.
|
|
112
|
+
|
|
111
113
|
### 3.2 Register a project (once per project)
|
|
112
114
|
|
|
113
115
|
From the CLI:
|
package/package.json
CHANGED
package/runtime/BUILD.json
CHANGED
package/runtime/agents/SKILL.md
CHANGED
|
@@ -160,8 +160,9 @@ These phases are governed by [okstra-team-contract](./skills/okstra-team-contrac
|
|
|
160
160
|
|
|
161
161
|
1. Call `TeamCreate(team_name: "okstra-<task-key>", description: "Lead-plus-worker okstra run for <task-key>")`.
|
|
162
162
|
2. Record the `TeamCreate` outcome in team-state under `teamCreate: { attempted: true, status: "ok"|"error", error?: <message> }` before any dispatch. This is the audit trail that justifies a later no-`team_name` fallback.
|
|
163
|
-
3. If `
|
|
164
|
-
4. If `TeamCreate`
|
|
163
|
+
3. Verify `team-state.lead.sessionId` is populated. The `okstra.sh` exec path fills it automatically (`generate_claude_session_id` → `claude --session-id ...`). The render-only / in-session takeover path (`okstra-run` skill) auto-detects the live session's jsonl via `resolve_inproc_lead_session_id`, but the detector is best-effort and may return empty if `~/.claude/projects/<encoded-cwd>/` is unreadable or has no jsonl yet. If `lead.sessionId` is empty at this point, write the running session's id into team-state before proceeding — Phase 7 token-usage collection depends on it and will fail with `lead jsonl not found (sessionId=)` otherwise.
|
|
164
|
+
4. If `TeamCreate` succeeds, proceed to Phase 4 (dispatch with `team_name`).
|
|
165
|
+
5. If `TeamCreate` fails (tool unavailable, permission denied, environment lacks Agent Teams support), proceed to Phase 5 fallback (dispatch with `run_in_background: true` and no `team_name`).
|
|
165
166
|
|
|
166
167
|
Use agent and subagent names that map cleanly to the selected worker roles. Do not create ambiguous role names that differ from `Claude worker`, `Codex worker`, `Gemini worker`, or `Report writer worker`.
|
|
167
168
|
|
|
@@ -14,6 +14,32 @@ Invoke the `okstra` skill now. Read the manifests below for all task metadata, p
|
|
|
14
14
|
- Phase advancement requires a new okstra invocation launched with `--task-type {{WORKFLOW_NEXT_RECOMMENDED_PHASE}}` after this run's final report is written and approved. The lead must not write source code, run builds/migrations/deployments, or otherwise produce artifacts of a different phase from inside this run.
|
|
15
15
|
- See `Lifecycle Phase Boundaries` in the okstra skill (`agents/SKILL.md`) for the canonical rules and the phase-transition checklist.
|
|
16
16
|
|
|
17
|
+
## Team Creation Gate (BLOCKING)
|
|
18
|
+
|
|
19
|
+
Before any `Agent` dispatch for workers, you MUST perform Phase 3 of the
|
|
20
|
+
`okstra` skill (`agents/SKILL.md` → "Phase 3 — Team creation"). Skipping
|
|
21
|
+
this gate silently degrades the run to in-process background dispatch and
|
|
22
|
+
loses the Teams split-pane observability surface, even though worker
|
|
23
|
+
outputs may still appear correct on disk.
|
|
24
|
+
|
|
25
|
+
Required actions, in order, regardless of how many workers are selected
|
|
26
|
+
for this run (roster comes from `resultContract.requiredWorkerRoles` in
|
|
27
|
+
`task-manifest.json` — it may be 1, 2, 3, or more workers):
|
|
28
|
+
|
|
29
|
+
1. Invoke the `okstra-team-contract` skill and verify the selected worker
|
|
30
|
+
roster against `task-manifest.json`'s `resultContract.requiredWorkerRoles`.
|
|
31
|
+
2. Call `TeamCreate(team_name: "okstra-{{TASK_KEY}}", description: ...)`.
|
|
32
|
+
3. Record the outcome in team-state under
|
|
33
|
+
`teamCreate: { attempted: true, status: "ok" | "error", error?: <msg> }`
|
|
34
|
+
BEFORE any `Agent(...)` worker dispatch.
|
|
35
|
+
4. Only after `teamCreate` is persisted may you dispatch workers — with
|
|
36
|
+
`team_name` on success, or with `run_in_background: true` and no
|
|
37
|
+
`team_name` ONLY when `teamCreate.status == "error"` was recorded.
|
|
38
|
+
|
|
39
|
+
If the Agent tool rejects a dispatch with `"team must be created first"` /
|
|
40
|
+
`"team을 먼저 생성하거나 team_name 없이 호출해야 합니다"`, the correct
|
|
41
|
+
response is to go back to step 2 — NOT to strip `team_name` and retry.
|
|
42
|
+
|
|
17
43
|
## Project Root
|
|
18
44
|
|
|
19
45
|
- Absolute project root: `{{PROJECT_ROOT}}`
|
|
@@ -51,7 +51,11 @@ from .seeding import (
|
|
|
51
51
|
render_runtime_settings_file,
|
|
52
52
|
verify_installation,
|
|
53
53
|
)
|
|
54
|
-
from .session import
|
|
54
|
+
from .session import (
|
|
55
|
+
generate_claude_session_id,
|
|
56
|
+
resolve_inproc_lead_session_id,
|
|
57
|
+
write_claude_resume_command_file,
|
|
58
|
+
)
|
|
55
59
|
from .workers import normalize_workers, resolve_profile_workers
|
|
56
60
|
from .workflow import compute_workflow_state
|
|
57
61
|
from .worktree import provision_implementation_worktree
|
|
@@ -537,7 +541,16 @@ def prepare_task_bundle(inp: PrepareInputs) -> PrepareOutputs:
|
|
|
537
541
|
"EXECUTOR_WORKTREE_NOTE": worktree.note,
|
|
538
542
|
})
|
|
539
543
|
|
|
540
|
-
|
|
544
|
+
if inp.render_only:
|
|
545
|
+
# render-only entry path (e.g. okstra-run skill, in-session takeover):
|
|
546
|
+
# the calling Claude session itself becomes the lead, so we must NOT
|
|
547
|
+
# mint a fresh UUID — instead, best-effort detect the live session's
|
|
548
|
+
# jsonl under ~/.claude/projects/<encoded-cwd>/. Leaving this blank
|
|
549
|
+
# caused Phase 7 token-usage collection to fail (lead jsonl not
|
|
550
|
+
# found) and the validator to report `sessionId=`.
|
|
551
|
+
claude_session_id = resolve_inproc_lead_session_id(project_root)
|
|
552
|
+
else:
|
|
553
|
+
claude_session_id = generate_claude_session_id()
|
|
541
554
|
|
|
542
555
|
# ---- material + related-tasks ----
|
|
543
556
|
profile_content = _expand_profile_includes(profile_file)
|
|
@@ -15,6 +15,39 @@ def generate_claude_session_id() -> str:
|
|
|
15
15
|
return str(uuid.uuid4())
|
|
16
16
|
|
|
17
17
|
|
|
18
|
+
def _claude_projects_dir_for(cwd: Path) -> Path:
|
|
19
|
+
"""Mirror of `okstra_token_usage.paths.claude_project_dir` — kept local to
|
|
20
|
+
avoid a cross-package import inside the run path.
|
|
21
|
+
"""
|
|
22
|
+
encoded = "-" + str(cwd).strip("/").replace("/", "-")
|
|
23
|
+
return Path.home() / ".claude" / "projects" / encoded
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def resolve_inproc_lead_session_id(project_root: Path) -> str:
|
|
27
|
+
"""Best-effort detection of the running Claude session's id when okstra is
|
|
28
|
+
invoked render-only from inside a live session (the `okstra-run` skill
|
|
29
|
+
path). The current session's jsonl is being actively written to under
|
|
30
|
+
`~/.claude/projects/<encoded-cwd>/`, so the most recently modified file
|
|
31
|
+
in that directory is, with very high probability, the calling session.
|
|
32
|
+
|
|
33
|
+
Returns the UUID stem on success, empty string on failure (directory
|
|
34
|
+
missing, no jsonl files, permission error). Callers must treat this as
|
|
35
|
+
best-effort — a failure does not invalidate the session, it just means
|
|
36
|
+
auto-detection could not confirm one.
|
|
37
|
+
"""
|
|
38
|
+
try:
|
|
39
|
+
d = _claude_projects_dir_for(project_root)
|
|
40
|
+
if not d.exists():
|
|
41
|
+
return ""
|
|
42
|
+
candidates = [p for p in d.iterdir() if p.suffix == ".jsonl"]
|
|
43
|
+
if not candidates:
|
|
44
|
+
return ""
|
|
45
|
+
newest = max(candidates, key=lambda p: p.stat().st_mtime)
|
|
46
|
+
return newest.stem
|
|
47
|
+
except OSError:
|
|
48
|
+
return ""
|
|
49
|
+
|
|
50
|
+
|
|
18
51
|
def write_claude_resume_command_file(
|
|
19
52
|
*, resume_command_path: Path, project_root: Path, claude_session_id: str,
|
|
20
53
|
) -> None:
|
|
@@ -14,13 +14,18 @@ description: Use when the user asks to list past okstra runs, check execution hi
|
|
|
14
14
|
## Step 0: Verify okstra runtime + project setup
|
|
15
15
|
|
|
16
16
|
```bash
|
|
17
|
-
|
|
17
|
+
if command -v okstra >/dev/null 2>&1; then
|
|
18
|
+
OKSTRA_CMD="okstra"
|
|
19
|
+
else
|
|
20
|
+
OKSTRA_CMD="npx -y okstra@latest"
|
|
21
|
+
fi
|
|
22
|
+
$OKSTRA_CMD ensure-installed >/dev/null 2>&1 || {
|
|
18
23
|
echo "FAIL: okstra not installed; tell the user to run: npx okstra@latest install" >&2
|
|
19
24
|
exit 1
|
|
20
25
|
}
|
|
21
|
-
eval "$(
|
|
26
|
+
eval "$($OKSTRA_CMD paths --shell)"
|
|
22
27
|
export PYTHONPATH="$OKSTRA_PYTHONPATH"
|
|
23
|
-
OKSTRA_PROJECT_INFO="$(
|
|
28
|
+
OKSTRA_PROJECT_INFO="$($OKSTRA_CMD check-project --json)" || {
|
|
24
29
|
echo "FAIL: this project has no okstra setup. Tell the user to run /okstra-setup first." >&2
|
|
25
30
|
echo "$OKSTRA_PROJECT_INFO" >&2
|
|
26
31
|
exit 1
|
|
@@ -15,13 +15,18 @@ user-invocable: false
|
|
|
15
15
|
## Step 0: Verify okstra runtime + project setup
|
|
16
16
|
|
|
17
17
|
```bash
|
|
18
|
-
|
|
18
|
+
if command -v okstra >/dev/null 2>&1; then
|
|
19
|
+
OKSTRA_CMD="okstra"
|
|
20
|
+
else
|
|
21
|
+
OKSTRA_CMD="npx -y okstra@latest"
|
|
22
|
+
fi
|
|
23
|
+
$OKSTRA_CMD ensure-installed >/dev/null 2>&1 || {
|
|
19
24
|
echo "FAIL: okstra not installed; tell the user to run: npx okstra@latest install" >&2
|
|
20
25
|
exit 1
|
|
21
26
|
}
|
|
22
|
-
eval "$(
|
|
27
|
+
eval "$($OKSTRA_CMD paths --shell)"
|
|
23
28
|
export PYTHONPATH="$OKSTRA_PYTHONPATH"
|
|
24
|
-
OKSTRA_PROJECT_INFO="$(
|
|
29
|
+
OKSTRA_PROJECT_INFO="$($OKSTRA_CMD check-project --json)" || {
|
|
25
30
|
echo "FAIL: this project has no okstra setup. Tell the user to run /okstra-setup first." >&2
|
|
26
31
|
echo "$OKSTRA_PROJECT_INFO" >&2
|
|
27
32
|
exit 1
|
|
@@ -36,18 +36,26 @@ Do NOT hard-code or guess any okstra path. Every run loads them fresh from
|
|
|
36
36
|
the single authority — `okstra`:
|
|
37
37
|
|
|
38
38
|
```bash
|
|
39
|
+
# 0) Resolve runner: prefer PATH (npm-installed) over npx (avoids per-call registry lookup).
|
|
40
|
+
# If the user installed okstra via npm, they control upgrade timing — do not force @latest.
|
|
41
|
+
if command -v okstra >/dev/null 2>&1; then
|
|
42
|
+
OKSTRA_CMD="okstra"
|
|
43
|
+
else
|
|
44
|
+
OKSTRA_CMD="npx -y okstra@latest"
|
|
45
|
+
fi
|
|
46
|
+
|
|
39
47
|
# 1) Ensure runtime is fresh (idempotent, cached when up-to-date)
|
|
40
|
-
|
|
48
|
+
$OKSTRA_CMD ensure-installed >/dev/null 2>&1 || {
|
|
41
49
|
echo "FAIL: okstra not installed; tell the user to run: npx okstra@latest install" >&2
|
|
42
50
|
exit 1
|
|
43
51
|
}
|
|
44
52
|
|
|
45
53
|
# 2) Load all runtime paths into the shell as OKSTRA_* exports
|
|
46
|
-
eval "$(
|
|
54
|
+
eval "$($OKSTRA_CMD paths --shell)"
|
|
47
55
|
export PYTHONPATH="$OKSTRA_PYTHONPATH"
|
|
48
56
|
|
|
49
57
|
# 3) Verify the current project has okstra metadata (project.json + projectId)
|
|
50
|
-
OKSTRA_PROJECT_INFO="$(
|
|
58
|
+
OKSTRA_PROJECT_INFO="$($OKSTRA_CMD check-project --json)" || {
|
|
51
59
|
echo "FAIL: this project has no okstra setup. Tell the user to run /okstra-setup first." >&2
|
|
52
60
|
echo "$OKSTRA_PROJECT_INFO" >&2
|
|
53
61
|
exit 1
|
|
@@ -40,13 +40,18 @@ If `--title` is omitted, derive a default title from `task-group` (e.g. `uploadF
|
|
|
40
40
|
Run before anything else in this skill:
|
|
41
41
|
|
|
42
42
|
```bash
|
|
43
|
-
|
|
43
|
+
if command -v okstra >/dev/null 2>&1; then
|
|
44
|
+
OKSTRA_CMD="okstra"
|
|
45
|
+
else
|
|
46
|
+
OKSTRA_CMD="npx -y okstra@latest"
|
|
47
|
+
fi
|
|
48
|
+
$OKSTRA_CMD ensure-installed >/dev/null 2>&1 || {
|
|
44
49
|
echo "FAIL: okstra not installed; tell the user to run: npx okstra@latest install" >&2
|
|
45
50
|
exit 1
|
|
46
51
|
}
|
|
47
|
-
eval "$(
|
|
52
|
+
eval "$($OKSTRA_CMD paths --shell)"
|
|
48
53
|
export PYTHONPATH="$OKSTRA_PYTHONPATH"
|
|
49
|
-
OKSTRA_PROJECT_INFO="$(
|
|
54
|
+
OKSTRA_PROJECT_INFO="$($OKSTRA_CMD check-project --json)" || {
|
|
50
55
|
echo "FAIL: this project has no okstra setup. Tell the user to run /okstra-setup first." >&2
|
|
51
56
|
echo "$OKSTRA_PROJECT_INFO" >&2
|
|
52
57
|
exit 1
|
|
@@ -55,7 +55,13 @@ running the legacy `okstra-install.sh` — that path is dev-only.
|
|
|
55
55
|
## Step 2: Load runtime paths
|
|
56
56
|
|
|
57
57
|
```bash
|
|
58
|
-
|
|
58
|
+
# Prefer PATH-resolved okstra (npm-installed) over npx — avoids per-call registry lookup.
|
|
59
|
+
if command -v okstra >/dev/null 2>&1; then
|
|
60
|
+
OKSTRA_CMD="okstra"
|
|
61
|
+
else
|
|
62
|
+
OKSTRA_CMD="npx -y okstra@latest"
|
|
63
|
+
fi
|
|
64
|
+
eval "$($OKSTRA_CMD paths --shell)"
|
|
59
65
|
export PYTHONPATH="$OKSTRA_PYTHONPATH"
|
|
60
66
|
```
|
|
61
67
|
|
|
@@ -113,7 +119,7 @@ PY
|
|
|
113
119
|
## Step 5: Verify
|
|
114
120
|
|
|
115
121
|
```bash
|
|
116
|
-
|
|
122
|
+
$OKSTRA_CMD doctor
|
|
117
123
|
```
|
|
118
124
|
|
|
119
125
|
If all checks return `OK`, the setup is complete. If any check fails, surface
|
|
@@ -17,13 +17,18 @@ Before any other step, ensure both the okstra runtime and the current
|
|
|
17
17
|
project's okstra metadata are in place:
|
|
18
18
|
|
|
19
19
|
```bash
|
|
20
|
-
|
|
20
|
+
if command -v okstra >/dev/null 2>&1; then
|
|
21
|
+
OKSTRA_CMD="okstra"
|
|
22
|
+
else
|
|
23
|
+
OKSTRA_CMD="npx -y okstra@latest"
|
|
24
|
+
fi
|
|
25
|
+
$OKSTRA_CMD ensure-installed >/dev/null 2>&1 || {
|
|
21
26
|
echo "FAIL: okstra not installed; tell the user to run: npx okstra@latest install" >&2
|
|
22
27
|
exit 1
|
|
23
28
|
}
|
|
24
|
-
eval "$(
|
|
29
|
+
eval "$($OKSTRA_CMD paths --shell)"
|
|
25
30
|
export PYTHONPATH="$OKSTRA_PYTHONPATH"
|
|
26
|
-
OKSTRA_PROJECT_INFO="$(
|
|
31
|
+
OKSTRA_PROJECT_INFO="$($OKSTRA_CMD check-project --json)" || {
|
|
27
32
|
echo "FAIL: this project has no okstra setup. Tell the user to run /okstra-setup first." >&2
|
|
28
33
|
echo "$OKSTRA_PROJECT_INFO" >&2
|
|
29
34
|
exit 1
|
|
@@ -30,13 +30,18 @@ If a run never reached Phase 7, its `team-state` will not have `durationMs` fill
|
|
|
30
30
|
## Step 0: Verify okstra runtime + project setup
|
|
31
31
|
|
|
32
32
|
```bash
|
|
33
|
-
|
|
33
|
+
if command -v okstra >/dev/null 2>&1; then
|
|
34
|
+
OKSTRA_CMD="okstra"
|
|
35
|
+
else
|
|
36
|
+
OKSTRA_CMD="npx -y okstra@latest"
|
|
37
|
+
fi
|
|
38
|
+
$OKSTRA_CMD ensure-installed >/dev/null 2>&1 || {
|
|
34
39
|
echo "FAIL: okstra not installed; tell the user to run: npx okstra@latest install" >&2
|
|
35
40
|
exit 1
|
|
36
41
|
}
|
|
37
|
-
eval "$(
|
|
42
|
+
eval "$($OKSTRA_CMD paths --shell)"
|
|
38
43
|
export PYTHONPATH="$OKSTRA_PYTHONPATH"
|
|
39
|
-
OKSTRA_PROJECT_INFO="$(
|
|
44
|
+
OKSTRA_PROJECT_INFO="$($OKSTRA_CMD check-project --json)" || {
|
|
40
45
|
echo "FAIL: this project has no okstra setup. Tell the user to run /okstra-setup first." >&2
|
|
41
46
|
echo "$OKSTRA_PROJECT_INFO" >&2
|
|
42
47
|
exit 1
|
|
@@ -324,6 +324,28 @@ def validate_team_state(
|
|
|
324
324
|
failures.append("team-state.workers must be a list")
|
|
325
325
|
return
|
|
326
326
|
|
|
327
|
+
dispatched_statuses = {"completed", "timeout", "error", "in-progress"}
|
|
328
|
+
any_dispatched = any(
|
|
329
|
+
isinstance(w, dict) and str(w.get("status", "")).strip() in dispatched_statuses
|
|
330
|
+
for w in workers
|
|
331
|
+
)
|
|
332
|
+
if any_dispatched:
|
|
333
|
+
team_create = team_state.get("teamCreate")
|
|
334
|
+
if not isinstance(team_create, dict) or not team_create.get("attempted"):
|
|
335
|
+
failures.append(
|
|
336
|
+
"team-state.teamCreate.attempted must be true once any worker has "
|
|
337
|
+
"been dispatched (status in completed/timeout/error/in-progress). "
|
|
338
|
+
"Phase 3 (TeamCreate) was skipped — workers ran in-process without "
|
|
339
|
+
"the Teams split-pane surface. See agents/SKILL.md Phase 3."
|
|
340
|
+
)
|
|
341
|
+
else:
|
|
342
|
+
tc_status = str(team_create.get("status", "")).strip()
|
|
343
|
+
if tc_status not in {"ok", "error"}:
|
|
344
|
+
failures.append(
|
|
345
|
+
"team-state.teamCreate.status must be `ok` or `error` once "
|
|
346
|
+
f"workers have been dispatched (found: `{tc_status}`)."
|
|
347
|
+
)
|
|
348
|
+
|
|
327
349
|
by_role: dict[str, dict] = {}
|
|
328
350
|
for worker in workers:
|
|
329
351
|
if not isinstance(worker, dict):
|