opencode-onboard 0.2.13 → 0.3.1
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/content/.agents/agents/back-engineer.md +8 -5
- package/content/.agents/agents/devops-manager.md +4 -0
- package/content/.agents/agents/front-engineer.md +8 -5
- package/content/.agents/agents/infra-engineer.md +15 -4
- package/content/.agents/agents/quality-engineer.md +9 -6
- package/content/.agents/agents/security-auditor.md +6 -4
- package/content/.agents/skills/ob-pullrequest-gh/SKILL.md +8 -3
- package/content/.agents/skills/ob-userstory-gh/SKILL.md +5 -5
- package/content/.opencode/commands/opsx-apply.md +14 -6
- package/content/.opencode/plugins/session-log.js +141 -13
- package/content/.opencode/skills/openspec-apply-change/SKILL.md +14 -6
- package/package.json +1 -1
- package/src/steps/init-openspec.js +14 -6
|
@@ -33,15 +33,17 @@ If `rtk` is not available, report it as a blocker. Do not run commands without i
|
|
|
33
33
|
|
|
34
34
|
Skills are located in `.agents/skills/`. Detect and use relevant skills automatically, the user will never tell you which skill to use.
|
|
35
35
|
|
|
36
|
-
1.
|
|
37
|
-
2.
|
|
38
|
-
3.
|
|
39
|
-
4.
|
|
36
|
+
1. If the spawn prompt lists specific skills to load, read those `SKILL.md` files FIRST before any implementation
|
|
37
|
+
2. Additionally, read the task and identify domain and platform
|
|
38
|
+
3. Scan `.agents/skills/` for available skills
|
|
39
|
+
4. Read each `SKILL.md` description to assess relevance
|
|
40
|
+
5. Load and follow any skill that applies, even partial match warrants loading
|
|
40
41
|
|
|
41
42
|
Rules:
|
|
42
43
|
- Never implement directly if a skill applies
|
|
43
44
|
- Follow skill instructions exactly, do not partially apply them
|
|
44
45
|
- If two skills apply, follow both, resolve conflicts by asking the lead
|
|
46
|
+
- Skills listed in the spawn prompt are MANDATORY, not optional
|
|
45
47
|
|
|
46
48
|
## Responsibilities
|
|
47
49
|
|
|
@@ -66,8 +68,9 @@ Rules:
|
|
|
66
68
|
## Workflow
|
|
67
69
|
|
|
68
70
|
When spawned by the lead:
|
|
71
|
+
0. Read ALL skills listed in the spawn prompt FIRST. Do not proceed until every listed SKILL.md has been read. Reply to lead with `team_message` confirming which skills were loaded.
|
|
69
72
|
1. For each assigned task: call `team_claim task_id:<id>` before starting
|
|
70
|
-
2. Implement the task
|
|
73
|
+
2. Implement the task following loaded skill rules
|
|
71
74
|
3. Call `team_tasks_complete task_id:<id>` after finishing
|
|
72
75
|
4. When all tasks are done or blocked, send results to lead via `team_message`
|
|
73
76
|
|
|
@@ -33,6 +33,9 @@ If `rtk` is not available, report it as a blocker. Do not run commands without i
|
|
|
33
33
|
|
|
34
34
|
Skills are located in `.agents/skills/`. Detect and use relevant skills automatically, the user will never tell you which skill to use.
|
|
35
35
|
|
|
36
|
+
1. If the spawn prompt lists specific skills to load, read those `SKILL.md` files FIRST before any implementation
|
|
37
|
+
2. Additionally, identify the platform from URLs or context
|
|
38
|
+
|
|
36
39
|
Examples of intent → skill mapping:
|
|
37
40
|
- URL contains `dev.azure.com` or `visualstudio.com` → look for `ob-userstory-az` or `ob-pullrequest-az`
|
|
38
41
|
- URL contains `github.com` → look for `ob-userstory-gh` or `ob-pullrequest-gh`
|
|
@@ -43,6 +46,7 @@ Rules:
|
|
|
43
46
|
- Never interact with a platform without loading the matching skill first
|
|
44
47
|
- Follow skill instructions exactly, do not partially apply them
|
|
45
48
|
- If no skill exists for the platform, report it as a blocker rather than improvising
|
|
49
|
+
- Skills listed in the spawn prompt are MANDATORY, not optional
|
|
46
50
|
|
|
47
51
|
## Two Modes
|
|
48
52
|
|
|
@@ -33,15 +33,17 @@ If `rtk` is not available, report it as a blocker. Do not run commands without i
|
|
|
33
33
|
|
|
34
34
|
Skills are located in `.agents/skills/`. Detect and use relevant skills automatically, the user will never tell you which skill to use.
|
|
35
35
|
|
|
36
|
-
1.
|
|
37
|
-
2.
|
|
38
|
-
3.
|
|
39
|
-
4.
|
|
36
|
+
1. If the spawn prompt lists specific skills to load, read those `SKILL.md` files FIRST before any implementation
|
|
37
|
+
2. Additionally, read the task and identify domain and platform
|
|
38
|
+
3. Scan `.agents/skills/` for available skills
|
|
39
|
+
4. Read each `SKILL.md` description to assess relevance
|
|
40
|
+
5. Load and follow any skill that applies, even partial match warrants loading
|
|
40
41
|
|
|
41
42
|
Rules:
|
|
42
43
|
- Never implement directly if a skill applies
|
|
43
44
|
- Follow skill instructions exactly, do not partially apply them
|
|
44
45
|
- If two skills apply, follow both, resolve conflicts by asking the lead
|
|
46
|
+
- Skills listed in the spawn prompt are MANDATORY, not optional
|
|
45
47
|
|
|
46
48
|
## Responsibilities
|
|
47
49
|
|
|
@@ -65,8 +67,9 @@ Rules:
|
|
|
65
67
|
## Workflow
|
|
66
68
|
|
|
67
69
|
When spawned by the lead:
|
|
70
|
+
0. Read ALL skills listed in the spawn prompt FIRST. Do not proceed until every listed SKILL.md has been read. Reply to lead with `team_message` confirming which skills were loaded.
|
|
68
71
|
1. For each assigned task: call `team_claim task_id:<id>` before starting
|
|
69
|
-
2. Implement the task
|
|
72
|
+
2. Implement the task following loaded skill rules
|
|
70
73
|
3. Call `team_tasks_complete task_id:<id>` after finishing
|
|
71
74
|
4. When all tasks are done or blocked, send results to lead via `team_message`
|
|
72
75
|
|
|
@@ -33,15 +33,17 @@ If `rtk` is not available, report it as a blocker. Do not run commands without i
|
|
|
33
33
|
|
|
34
34
|
Skills are located in `.agents/skills/`. Detect and use relevant skills automatically, the user will never tell you which skill to use.
|
|
35
35
|
|
|
36
|
-
1.
|
|
37
|
-
2.
|
|
38
|
-
3.
|
|
39
|
-
4.
|
|
36
|
+
1. If the spawn prompt lists specific skills to load, read those `SKILL.md` files FIRST before any implementation
|
|
37
|
+
2. Additionally, read the task and identify domain and platform
|
|
38
|
+
3. Scan `.agents/skills/` for available skills
|
|
39
|
+
4. Read each `SKILL.md` description to assess relevance
|
|
40
|
+
5. Load and follow any skill that applies, even partial match warrants loading
|
|
40
41
|
|
|
41
42
|
Rules:
|
|
42
43
|
- Never implement directly if a skill applies
|
|
43
44
|
- Follow skill instructions exactly, do not partially apply them
|
|
44
45
|
- If two skills apply, follow both, resolve conflicts by asking the lead
|
|
46
|
+
- Skills listed in the spawn prompt are MANDATORY, not optional
|
|
45
47
|
|
|
46
48
|
## Responsibilities
|
|
47
49
|
|
|
@@ -62,6 +64,15 @@ Rules:
|
|
|
62
64
|
- Do not force push
|
|
63
65
|
- Report blockers immediately rather than working around them
|
|
64
66
|
|
|
67
|
+
## Workflow
|
|
68
|
+
|
|
69
|
+
When spawned by the lead:
|
|
70
|
+
0. Read ALL skills listed in the spawn prompt FIRST. Do not proceed until every listed SKILL.md has been read. Reply to lead with `team_message` confirming which skills were loaded.
|
|
71
|
+
1. For each assigned task: call `team_claim task_id:<id>` before starting
|
|
72
|
+
2. Implement the task following loaded skill rules
|
|
73
|
+
3. Call `team_tasks_complete task_id:<id>` after finishing
|
|
74
|
+
4. When all tasks are done or blocked, send results to lead via `team_message`
|
|
75
|
+
|
|
65
76
|
## Output Format
|
|
66
77
|
|
|
67
78
|
```
|
|
@@ -33,15 +33,17 @@ If `rtk` is not available, report it as a blocker. Do not run commands without i
|
|
|
33
33
|
|
|
34
34
|
Skills are located in `.agents/skills/`. Detect and use relevant skills automatically, the user will never tell you which skill to use.
|
|
35
35
|
|
|
36
|
-
1.
|
|
37
|
-
2.
|
|
38
|
-
3.
|
|
39
|
-
4.
|
|
36
|
+
1. If the spawn prompt lists specific skills to load, read those `SKILL.md` files FIRST before any implementation
|
|
37
|
+
2. Additionally, read the task and identify domain and platform
|
|
38
|
+
3. Scan `.agents/skills/` for available skills
|
|
39
|
+
4. Read each `SKILL.md` description to assess relevance
|
|
40
|
+
5. Load and follow any skill that applies, even partial match warrants loading
|
|
40
41
|
|
|
41
42
|
Rules:
|
|
42
43
|
- Never implement directly if a skill applies
|
|
43
44
|
- Follow skill instructions exactly, do not partially apply them
|
|
44
45
|
- If two skills apply, follow both, resolve conflicts by asking the lead
|
|
46
|
+
- Skills listed in the spawn prompt are MANDATORY, not optional
|
|
45
47
|
|
|
46
48
|
## Responsibilities
|
|
47
49
|
|
|
@@ -64,8 +66,9 @@ Rules:
|
|
|
64
66
|
|
|
65
67
|
When spawned by the lead:
|
|
66
68
|
1. Read the task list and context files provided in the spawn prompt
|
|
67
|
-
2.
|
|
68
|
-
3.
|
|
69
|
+
2. If no board task is assigned, do not block on team_claim. Run verification directly from the spawn prompt scope.
|
|
70
|
+
3. Run tests, build, lint, and verify acceptance criteria
|
|
71
|
+
4. When done, send results to lead via `team_message`
|
|
69
72
|
|
|
70
73
|
## Output Format
|
|
71
74
|
|
|
@@ -31,15 +31,17 @@ If `rtk` is not available, report it as a blocker. Do not run commands without i
|
|
|
31
31
|
|
|
32
32
|
Skills are located in `.agents/skills/`. Detect and use relevant skills automatically, the user will never tell you which skill to use.
|
|
33
33
|
|
|
34
|
-
1.
|
|
35
|
-
2.
|
|
36
|
-
3.
|
|
37
|
-
4.
|
|
34
|
+
1. If the spawn prompt lists specific skills to load, read those `SKILL.md` files FIRST before any implementation
|
|
35
|
+
2. Additionally, read the task and identify domain and platform
|
|
36
|
+
3. Scan `.agents/skills/` for available skills
|
|
37
|
+
4. Read each `SKILL.md` description to assess relevance
|
|
38
|
+
5. Load and follow any skill that applies, even partial match warrants loading
|
|
38
39
|
|
|
39
40
|
Rules:
|
|
40
41
|
- Never implement directly if a skill applies
|
|
41
42
|
- Follow skill instructions exactly, do not partially apply them
|
|
42
43
|
- If two skills apply, follow both, resolve conflicts by asking the lead
|
|
44
|
+
- Skills listed in the spawn prompt are MANDATORY, not optional
|
|
43
45
|
|
|
44
46
|
## Responsibilities
|
|
45
47
|
|
|
@@ -65,14 +65,19 @@ rtk gh pr create \
|
|
|
65
65
|
|
|
66
66
|
### Step 5: Post screenshot comment
|
|
67
67
|
|
|
68
|
-
|
|
68
|
+
Resolve commit SHA (the commit that includes screenshots):
|
|
69
|
+
```bash
|
|
70
|
+
rtk git rev-parse HEAD
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
Build blob URL for each image (preferred, stable in PR discussion):
|
|
69
74
|
```
|
|
70
|
-
https://
|
|
75
|
+
https://github.com/{owner}/{repo}/blob/{sha}/openspec/changes/{change}/images/{file}.png
|
|
71
76
|
```
|
|
72
77
|
|
|
73
78
|
Post comment:
|
|
74
79
|
```bash
|
|
75
|
-
rtk gh pr comment {pr-number} --body $'## Screenshots\n\n'
|
|
76
81
|
```
|
|
77
82
|
|
|
78
83
|
---
|
|
@@ -80,16 +80,16 @@ rtk gh issue edit <number> --repo {owner}/{repo} --add-label "in-progress"
|
|
|
80
80
|
|
|
81
81
|
## Screenshot / Image Strategy
|
|
82
82
|
|
|
83
|
-
**Never embed images as attachments.** Save to openspec change folder and reference via
|
|
83
|
+
**Never embed images as attachments.** Save to openspec change folder and reference via GitHub blob URL pinned to commit SHA.
|
|
84
84
|
|
|
85
85
|
### Save location
|
|
86
86
|
```
|
|
87
87
|
openspec/changes/{change-name}/images/{screenshot}.png
|
|
88
88
|
```
|
|
89
89
|
|
|
90
|
-
###
|
|
90
|
+
### Blob URL format (preferred)
|
|
91
91
|
```
|
|
92
|
-
https://
|
|
92
|
+
https://github.com/{owner}/{repo}/blob/{sha}/openspec/changes/{change}/images/{file}.png
|
|
93
93
|
```
|
|
94
94
|
|
|
95
95
|
---
|
|
@@ -103,8 +103,8 @@ https://github.com/{owner}/{repo}/issues/{number}
|
|
|
103
103
|
# PR
|
|
104
104
|
https://github.com/{owner}/{repo}/pull/{number}
|
|
105
105
|
|
|
106
|
-
#
|
|
107
|
-
https://
|
|
106
|
+
# Blob file
|
|
107
|
+
https://github.com/{owner}/{repo}/blob/{sha}/{path}
|
|
108
108
|
```
|
|
109
109
|
|
|
110
110
|
---
|
|
@@ -86,7 +86,14 @@ Implement tasks from an OpenSpec change using the ensemble agent team.
|
|
|
86
86
|
DO NOT call team_claim yourself, only agents claim tasks.
|
|
87
87
|
DO NOT proceed to 6d until team_tasks_add succeeds.
|
|
88
88
|
|
|
89
|
-
**Step 6d.**
|
|
89
|
+
**Step 6d.** Discover relevant skills, then spawn specialists.
|
|
90
|
+
|
|
91
|
+
Before spawning, scan `.agents/skills/` and read each `SKILL.md` description line.
|
|
92
|
+
Match skills to agents by domain:
|
|
93
|
+
- front-engineer: UI, components, framework skills (e.g. next-best-practices, browser-automation)
|
|
94
|
+
- back-engineer: API, data, service skills
|
|
95
|
+
- infra-engineer: cloud, pipeline, deployment skills
|
|
96
|
+
- quality-engineer: testing, coverage skills
|
|
90
97
|
|
|
91
98
|
Each team_spawn MUST include the agent field (required, causes NOT NULL error if omitted).
|
|
92
99
|
|
|
@@ -97,8 +104,9 @@ Implement tasks from an OpenSpec change using the ensemble agent team.
|
|
|
97
104
|
4. The 6 OpenCode tools they have available (these are OpenCode tools, NOT shell commands, call them directly as tools, never via bash):
|
|
98
105
|
team_claim, team_tasks_complete, team_tasks_list, team_tasks_add, team_message, team_broadcast
|
|
99
106
|
5. How to proceed: call team_claim tool with the task_id to claim a task before starting it, call team_tasks_complete tool after finishing it, repeat until all their tasks are done, then call team_message tool to notify lead with results or blockers
|
|
107
|
+
6. Which skills to load: list the skill names and paths they MUST read before implementing. Example: "Before starting, read `.agents/skills/next-best-practices/SKILL.md` and follow its rules for all Next.js code."
|
|
100
108
|
|
|
101
|
-
Keep spawn prompts under
|
|
109
|
+
Keep spawn prompts under 600 tokens. Do not describe team internals or how ensemble works.
|
|
102
110
|
Only spawn agents whose tasks are actually needed by this change. Skip agents with no tasks.
|
|
103
111
|
|
|
104
112
|
First spawn all agents (wait for each team_spawn to confirm before the next):
|
|
@@ -113,9 +121,9 @@ Implement tasks from an OpenSpec change using the ensemble agent team.
|
|
|
113
121
|
|
|
114
122
|
Then immediately send each spawned agent a start message to kick them off:
|
|
115
123
|
```
|
|
116
|
-
team_message to:"back" text:"Start now.
|
|
117
|
-
team_message to:"front" text:"Start now.
|
|
118
|
-
team_message to:"infra" text:"Start now.
|
|
124
|
+
team_message to:"back" text:"Start now. Read all skills listed in your prompt first, confirm loaded skills, then claim your first task with team_claim."
|
|
125
|
+
team_message to:"front" text:"Start now. Read all skills listed in your prompt first, confirm loaded skills, then claim your first task with team_claim."
|
|
126
|
+
team_message to:"infra" text:"Start now. Read all skills listed in your prompt first, confirm loaded skills, then claim your first task with team_claim."
|
|
119
127
|
```
|
|
120
128
|
|
|
121
129
|
**Step 6e.** After sending start messages, tell the user what is running, then STOP and wait.
|
|
@@ -131,7 +139,7 @@ Implement tasks from an OpenSpec change using the ensemble agent team.
|
|
|
131
139
|
|
|
132
140
|
Spawn quality engineer with worktree:false (read-only, no file edits):
|
|
133
141
|
```
|
|
134
|
-
team_spawn name:"quality" agent:"quality-engineer" worktree:false prompt:"<
|
|
142
|
+
team_spawn name:"quality" agent:"quality-engineer" worktree:false prompt:"<verification scope, context summary, run tests + build + lint + verify acceptance criteria, no task claiming required in this phase, send results to lead when done>"
|
|
135
143
|
```
|
|
136
144
|
Wait for message → team_results → fix blockers → team_shutdown (no team_merge needed, worktree:false)
|
|
137
145
|
|
|
@@ -2,7 +2,8 @@ import fs from "node:fs"
|
|
|
2
2
|
import path from "node:path"
|
|
3
3
|
|
|
4
4
|
const LOG_FILE = ".agents/session-log.json"
|
|
5
|
-
const SPAWN_MATCH_WINDOW_MS =
|
|
5
|
+
const SPAWN_MATCH_WINDOW_MS = 30000
|
|
6
|
+
const UNMATCHED_SESSION_WARN_MS = 30000
|
|
6
7
|
const DEBUG = process.env.SESSION_LOG_DEBUG === "true"
|
|
7
8
|
|
|
8
9
|
// Per-session state
|
|
@@ -12,8 +13,12 @@ const sessionState = new Map()
|
|
|
12
13
|
const leadTeamBySession = new Map()
|
|
13
14
|
|
|
14
15
|
// Pending spawn records waiting for a session.created match
|
|
16
|
+
// Each entry: { leadSessionId, at, member, agentType, teamName, spawnedSessionId, intendedSkills }
|
|
15
17
|
const pendingSpawns = []
|
|
16
18
|
|
|
19
|
+
// spawnedSessionId -> pending spawn (direct correlation fast path)
|
|
20
|
+
const pendingSpawnBySessionId = new Map()
|
|
21
|
+
|
|
17
22
|
// Team -> completed session snapshots
|
|
18
23
|
const completedByTeam = new Map()
|
|
19
24
|
|
|
@@ -47,6 +52,11 @@ function resolveAgentName(session) {
|
|
|
47
52
|
return "lead"
|
|
48
53
|
}
|
|
49
54
|
|
|
55
|
+
function resolveModel(session) {
|
|
56
|
+
// Try common field names used by OpenCode session objects
|
|
57
|
+
return session?.model || session?.modelId || session?.model_id || null
|
|
58
|
+
}
|
|
59
|
+
|
|
50
60
|
function addSkillToState(state, skillName) {
|
|
51
61
|
if (!skillName || !state) return false
|
|
52
62
|
if (!state.skills) state.skills = new Set()
|
|
@@ -55,6 +65,37 @@ function addSkillToState(state, skillName) {
|
|
|
55
65
|
return true
|
|
56
66
|
}
|
|
57
67
|
|
|
68
|
+
// Extract skill names hinted in a spawn prompt by scanning for known skill-like tokens.
|
|
69
|
+
// Matches strings that look like skill directory names (kebab-case words after "skill" keyword
|
|
70
|
+
// or adjacent to known skill name patterns).
|
|
71
|
+
function extractIntendedSkills(prompt) {
|
|
72
|
+
if (!prompt || typeof prompt !== "string") return []
|
|
73
|
+
const skills = new Set()
|
|
74
|
+
|
|
75
|
+
// Match explicit skill name mentions: e.g. "ob-pullrequest-gh", "browser-automation"
|
|
76
|
+
const kebabPattern = /\b([a-z][a-z0-9]*(?:-[a-z0-9]+){1,})\b/g
|
|
77
|
+
let m
|
|
78
|
+
while ((m = kebabPattern.exec(prompt)) !== null) {
|
|
79
|
+
const candidate = m[1]
|
|
80
|
+
// Filter to plausible skill names: 2+ segments, known prefixes or suffixes
|
|
81
|
+
if (
|
|
82
|
+
candidate.startsWith("ob-") ||
|
|
83
|
+
candidate.startsWith("openspec-") ||
|
|
84
|
+
candidate.startsWith("browser-") ||
|
|
85
|
+
candidate.endsWith("-gh") ||
|
|
86
|
+
candidate.endsWith("-az") ||
|
|
87
|
+
candidate.endsWith("-automation") ||
|
|
88
|
+
candidate.endsWith("-change") ||
|
|
89
|
+
candidate.endsWith("-engineer") ||
|
|
90
|
+
candidate.endsWith("-manager") ||
|
|
91
|
+
candidate.endsWith("-auditor")
|
|
92
|
+
) {
|
|
93
|
+
skills.add(candidate)
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
return Array.from(skills).sort()
|
|
97
|
+
}
|
|
98
|
+
|
|
58
99
|
function toNum(v) {
|
|
59
100
|
if (typeof v === "number" && Number.isFinite(v)) return v
|
|
60
101
|
if (typeof v === "string" && v.trim() !== "" && !Number.isNaN(Number(v))) return Number(v)
|
|
@@ -133,7 +174,9 @@ function buildCompletedSnapshot(state, sessionId) {
|
|
|
133
174
|
member: state.member || null,
|
|
134
175
|
agentType: state.agentType || null,
|
|
135
176
|
team: state.teamName || null,
|
|
177
|
+
model: state.model || null,
|
|
136
178
|
skills: Array.from(state.skills || []).sort(),
|
|
179
|
+
intendedSkills: state.intendedSkills || [],
|
|
137
180
|
usage: usagePayload(state),
|
|
138
181
|
filesEdited: state.editCount || 0,
|
|
139
182
|
}
|
|
@@ -144,14 +187,21 @@ function buildTeamSkillsSummary(teamName) {
|
|
|
144
187
|
const byAgent = {}
|
|
145
188
|
for (const row of rows) {
|
|
146
189
|
const key = row.member || row.agent || "unknown"
|
|
147
|
-
if (!byAgent[key]) byAgent[key] = { agentType: row.agentType || null, skills: new Set() }
|
|
190
|
+
if (!byAgent[key]) byAgent[key] = { agentType: row.agentType || null, model: row.model || null, skills: new Set(), intendedSkills: new Set() }
|
|
148
191
|
for (const s of row.skills || []) byAgent[key].skills.add(s)
|
|
192
|
+
for (const s of row.intendedSkills || []) byAgent[key].intendedSkills.add(s)
|
|
149
193
|
}
|
|
150
194
|
const out = {}
|
|
151
195
|
for (const [k, v] of Object.entries(byAgent)) {
|
|
196
|
+
const skills = Array.from(v.skills).sort()
|
|
197
|
+
const intendedSkills = Array.from(v.intendedSkills).sort()
|
|
198
|
+
const missingSkills = intendedSkills.filter(s => !v.skills.has(s))
|
|
152
199
|
out[k] = {
|
|
153
200
|
agentType: v.agentType,
|
|
154
|
-
|
|
201
|
+
model: v.model,
|
|
202
|
+
skills,
|
|
203
|
+
intendedSkills,
|
|
204
|
+
missingSkills: missingSkills.length > 0 ? missingSkills : undefined,
|
|
155
205
|
}
|
|
156
206
|
}
|
|
157
207
|
return out
|
|
@@ -163,21 +213,52 @@ function trackCompletedByTeam(snapshot) {
|
|
|
163
213
|
completedByTeam.get(snapshot.team).push(snapshot)
|
|
164
214
|
}
|
|
165
215
|
|
|
166
|
-
function enqueuePendingSpawn(leadSessionId, args) {
|
|
167
|
-
|
|
216
|
+
function enqueuePendingSpawn(leadSessionId, args, spawnOutput) {
|
|
217
|
+
// Try to extract spawnedSessionId from team_spawn output for direct correlation
|
|
218
|
+
const spawnedSessionId =
|
|
219
|
+
spawnOutput?.sessionId ||
|
|
220
|
+
spawnOutput?.session_id ||
|
|
221
|
+
spawnOutput?.id ||
|
|
222
|
+
spawnOutput?.data?.sessionId ||
|
|
223
|
+
spawnOutput?.data?.session_id ||
|
|
224
|
+
spawnOutput?.data?.id ||
|
|
225
|
+
null
|
|
226
|
+
|
|
227
|
+
const record = {
|
|
168
228
|
leadSessionId,
|
|
169
229
|
at: nowMs(),
|
|
170
230
|
member: args?.name || null,
|
|
171
231
|
agentType: args?.agent || null,
|
|
172
232
|
teamName: leadTeamBySession.get(leadSessionId) || null,
|
|
173
|
-
|
|
233
|
+
spawnedSessionId,
|
|
234
|
+
intendedSkills: extractIntendedSkills(args?.prompt),
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
pendingSpawns.push(record)
|
|
238
|
+
|
|
239
|
+
if (spawnedSessionId) {
|
|
240
|
+
pendingSpawnBySessionId.set(spawnedSessionId, record)
|
|
241
|
+
}
|
|
174
242
|
}
|
|
175
243
|
|
|
176
|
-
function matchPendingSpawn() {
|
|
244
|
+
function matchPendingSpawn(sessionId) {
|
|
177
245
|
const now = nowMs()
|
|
178
|
-
|
|
246
|
+
|
|
247
|
+
// Fast path: direct session ID correlation from team_spawn output
|
|
248
|
+
if (sessionId && pendingSpawnBySessionId.has(sessionId)) {
|
|
249
|
+
const record = pendingSpawnBySessionId.get(sessionId)
|
|
250
|
+
pendingSpawnBySessionId.delete(sessionId)
|
|
251
|
+
const idx = pendingSpawns.indexOf(record)
|
|
252
|
+
if (idx !== -1) pendingSpawns.splice(idx, 1)
|
|
253
|
+
return record
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
// Fallback: time-window heuristic (drop expired first)
|
|
179
257
|
for (let i = pendingSpawns.length - 1; i >= 0; i--) {
|
|
180
|
-
if (now - pendingSpawns[i].at > SPAWN_MATCH_WINDOW_MS)
|
|
258
|
+
if (now - pendingSpawns[i].at > SPAWN_MATCH_WINDOW_MS) {
|
|
259
|
+
pendingSpawnBySessionId.delete(pendingSpawns[i].spawnedSessionId)
|
|
260
|
+
pendingSpawns.splice(i, 1)
|
|
261
|
+
}
|
|
181
262
|
}
|
|
182
263
|
if (pendingSpawns.length === 0) return null
|
|
183
264
|
return pendingSpawns.shift()
|
|
@@ -210,13 +291,16 @@ export const SessionLogPlugin = async ({ client, directory }) => {
|
|
|
210
291
|
const res = await client.session.get({ path: { id: sessionId } })
|
|
211
292
|
const session = res?.data
|
|
212
293
|
const fallbackAgent = resolveAgentName(session)
|
|
213
|
-
const
|
|
294
|
+
const model = resolveModel(session)
|
|
295
|
+
const spawnMatch = matchPendingSpawn(sessionId)
|
|
214
296
|
|
|
215
297
|
const state = {
|
|
216
298
|
agentName: spawnMatch?.member || fallbackAgent,
|
|
217
299
|
member: spawnMatch?.member || null,
|
|
218
300
|
agentType: spawnMatch?.agentType || null,
|
|
219
301
|
teamName: spawnMatch?.teamName || null,
|
|
302
|
+
model,
|
|
303
|
+
intendedSkills: spawnMatch?.intendedSkills || [],
|
|
220
304
|
editCount: 0,
|
|
221
305
|
skills: new Set(),
|
|
222
306
|
startedAtMs: nowMs(),
|
|
@@ -226,18 +310,56 @@ export const SessionLogPlugin = async ({ client, directory }) => {
|
|
|
226
310
|
reportedInputTokens: 0,
|
|
227
311
|
reportedOutputTokens: 0,
|
|
228
312
|
reportedTotalTokens: 0,
|
|
313
|
+
// Track whether this session was matched to a spawn record
|
|
314
|
+
spawnMatched: !!spawnMatch,
|
|
229
315
|
}
|
|
230
316
|
|
|
231
317
|
sessionState.set(sessionId, state)
|
|
232
|
-
|
|
318
|
+
|
|
319
|
+
const startedEntry = {
|
|
233
320
|
ts: ts(),
|
|
234
321
|
agent: state.agentName,
|
|
235
322
|
member: state.member,
|
|
236
323
|
agentType: state.agentType,
|
|
237
324
|
team: state.teamName,
|
|
325
|
+
model: state.model,
|
|
238
326
|
action: "started",
|
|
239
327
|
sessionId,
|
|
240
|
-
}
|
|
328
|
+
}
|
|
329
|
+
appendEntry(directory, startedEntry)
|
|
330
|
+
|
|
331
|
+
// Emit explicit teammate-registered entry when a spawn is matched
|
|
332
|
+
if (spawnMatch) {
|
|
333
|
+
appendEntry(directory, {
|
|
334
|
+
ts: ts(),
|
|
335
|
+
agent: state.agentName,
|
|
336
|
+
member: state.member,
|
|
337
|
+
agentType: state.agentType,
|
|
338
|
+
team: state.teamName,
|
|
339
|
+
model: state.model,
|
|
340
|
+
intendedSkills: state.intendedSkills,
|
|
341
|
+
action: "teammate-registered",
|
|
342
|
+
sessionId,
|
|
343
|
+
correlationMethod: spawnMatch.spawnedSessionId === sessionId ? "direct" : "time-window",
|
|
344
|
+
})
|
|
345
|
+
} else {
|
|
346
|
+
// No spawn match — schedule an unmatched-session warning
|
|
347
|
+
const capturedSessionId = sessionId
|
|
348
|
+
setTimeout(() => {
|
|
349
|
+
const s = sessionState.get(capturedSessionId)
|
|
350
|
+
if (!s || s.spawnMatched) return
|
|
351
|
+
appendEntry(directory, {
|
|
352
|
+
ts: ts(),
|
|
353
|
+
agent: s.agentName,
|
|
354
|
+
member: s.member,
|
|
355
|
+
team: s.teamName,
|
|
356
|
+
model: s.model,
|
|
357
|
+
action: "unmatched-session",
|
|
358
|
+
sessionId: capturedSessionId,
|
|
359
|
+
warning: "Session started with no matching team_spawn record. Agent identity may be inaccurate.",
|
|
360
|
+
})
|
|
361
|
+
}, UNMATCHED_SESSION_WARN_MS)
|
|
362
|
+
}
|
|
241
363
|
}
|
|
242
364
|
|
|
243
365
|
if (event?.type === "file.edited") {
|
|
@@ -261,9 +383,11 @@ export const SessionLogPlugin = async ({ client, directory }) => {
|
|
|
261
383
|
member: state.member,
|
|
262
384
|
agentType: state.agentType,
|
|
263
385
|
team: state.teamName,
|
|
386
|
+
model: state.model,
|
|
264
387
|
action: "completed",
|
|
265
388
|
filesEdited: state.editCount,
|
|
266
389
|
skills,
|
|
390
|
+
intendedSkills: state.intendedSkills,
|
|
267
391
|
usage,
|
|
268
392
|
})
|
|
269
393
|
|
|
@@ -321,6 +445,7 @@ export const SessionLogPlugin = async ({ client, directory }) => {
|
|
|
321
445
|
member: state.member,
|
|
322
446
|
agentType: state.agentType,
|
|
323
447
|
team: state.teamName,
|
|
448
|
+
model: state.model,
|
|
324
449
|
action: "skill-loaded",
|
|
325
450
|
skill: skillName,
|
|
326
451
|
source: "skill-tool",
|
|
@@ -343,6 +468,7 @@ export const SessionLogPlugin = async ({ client, directory }) => {
|
|
|
343
468
|
member: state.member,
|
|
344
469
|
agentType: state.agentType,
|
|
345
470
|
team: state.teamName,
|
|
471
|
+
model: state.model,
|
|
346
472
|
action: "skill-loaded",
|
|
347
473
|
skill: skillName,
|
|
348
474
|
source: "read-skill-file",
|
|
@@ -362,6 +488,7 @@ export const SessionLogPlugin = async ({ client, directory }) => {
|
|
|
362
488
|
member: state.member,
|
|
363
489
|
agentType: state.agentType,
|
|
364
490
|
team: state.teamName,
|
|
491
|
+
model: state.model,
|
|
365
492
|
...ensembleHandler(args),
|
|
366
493
|
}
|
|
367
494
|
appendEntry(directory, entry)
|
|
@@ -372,7 +499,7 @@ export const SessionLogPlugin = async ({ client, directory }) => {
|
|
|
372
499
|
}
|
|
373
500
|
|
|
374
501
|
if (tool === "team_spawn") {
|
|
375
|
-
enqueuePendingSpawn(sessionId, args)
|
|
502
|
+
enqueuePendingSpawn(sessionId, args, output)
|
|
376
503
|
}
|
|
377
504
|
|
|
378
505
|
if (tool === "team_cleanup") {
|
|
@@ -380,6 +507,7 @@ export const SessionLogPlugin = async ({ client, directory }) => {
|
|
|
380
507
|
appendEntry(directory, {
|
|
381
508
|
ts: ts(),
|
|
382
509
|
agent: state.agentName,
|
|
510
|
+
model: state.model,
|
|
383
511
|
action: "team-skills-summary",
|
|
384
512
|
team: teamName || null,
|
|
385
513
|
byAgent: teamName ? buildTeamSkillsSummary(teamName) : {},
|
|
@@ -92,7 +92,14 @@ Implement tasks from an OpenSpec change using the ensemble agent team.
|
|
|
92
92
|
DO NOT call team_claim yourself, only agents claim tasks.
|
|
93
93
|
DO NOT proceed to 6d until team_tasks_add succeeds.
|
|
94
94
|
|
|
95
|
-
**Step 6d.**
|
|
95
|
+
**Step 6d.** Discover relevant skills, then spawn specialists.
|
|
96
|
+
|
|
97
|
+
Before spawning, scan `.agents/skills/` and read each `SKILL.md` description line.
|
|
98
|
+
Match skills to agents by domain:
|
|
99
|
+
- front-engineer: UI, components, framework skills (e.g. next-best-practices, browser-automation)
|
|
100
|
+
- back-engineer: API, data, service skills
|
|
101
|
+
- infra-engineer: cloud, pipeline, deployment skills
|
|
102
|
+
- quality-engineer: testing, coverage skills
|
|
96
103
|
|
|
97
104
|
Each team_spawn MUST include the agent field (required, causes NOT NULL error if omitted).
|
|
98
105
|
|
|
@@ -103,8 +110,9 @@ Implement tasks from an OpenSpec change using the ensemble agent team.
|
|
|
103
110
|
4. The 6 OpenCode tools they have available (these are OpenCode tools, NOT shell commands, call them directly as tools, never via bash):
|
|
104
111
|
team_claim, team_tasks_complete, team_tasks_list, team_tasks_add, team_message, team_broadcast
|
|
105
112
|
5. How to proceed: call team_claim tool with the task_id to claim a task before starting it, call team_tasks_complete tool after finishing it, repeat until all their tasks are done, then call team_message tool to notify lead with results or blockers
|
|
113
|
+
6. Which skills to load: list the skill names and paths they MUST read before implementing. Example: "Before starting, read `.agents/skills/next-best-practices/SKILL.md` and follow its rules for all Next.js code."
|
|
106
114
|
|
|
107
|
-
Keep spawn prompts under
|
|
115
|
+
Keep spawn prompts under 600 tokens. Do not describe team internals or how ensemble works.
|
|
108
116
|
Only spawn agents whose tasks are actually needed by this change. Skip agents with no tasks.
|
|
109
117
|
|
|
110
118
|
First spawn all agents (wait for each team_spawn to confirm before the next):
|
|
@@ -119,9 +127,9 @@ Implement tasks from an OpenSpec change using the ensemble agent team.
|
|
|
119
127
|
|
|
120
128
|
Then immediately send each spawned agent a start message to kick them off:
|
|
121
129
|
```
|
|
122
|
-
team_message to:"back" text:"Start now.
|
|
123
|
-
team_message to:"front" text:"Start now.
|
|
124
|
-
team_message to:"infra" text:"Start now.
|
|
130
|
+
team_message to:"back" text:"Start now. Read all skills listed in your prompt first, confirm loaded skills, then claim your first task with team_claim."
|
|
131
|
+
team_message to:"front" text:"Start now. Read all skills listed in your prompt first, confirm loaded skills, then claim your first task with team_claim."
|
|
132
|
+
team_message to:"infra" text:"Start now. Read all skills listed in your prompt first, confirm loaded skills, then claim your first task with team_claim."
|
|
125
133
|
```
|
|
126
134
|
|
|
127
135
|
**Step 6e.** After sending start messages, tell the user what is running, then STOP and wait.
|
|
@@ -137,7 +145,7 @@ Implement tasks from an OpenSpec change using the ensemble agent team.
|
|
|
137
145
|
|
|
138
146
|
Spawn quality engineer with worktree:false (read-only, no file edits):
|
|
139
147
|
```
|
|
140
|
-
team_spawn name:"quality" agent:"quality-engineer" worktree:false prompt:"<
|
|
148
|
+
team_spawn name:"quality" agent:"quality-engineer" worktree:false prompt:"<verification scope, context summary, run tests + build + lint + verify acceptance criteria, no task claiming required in this phase, send results to lead when done>"
|
|
141
149
|
```
|
|
142
150
|
Wait for message → team_results → fix blockers → team_shutdown (no team_merge needed, worktree:false)
|
|
143
151
|
|
package/package.json
CHANGED
|
@@ -38,7 +38,14 @@ const ENSEMBLE_SECTION = `6. **Implement via ensemble team**
|
|
|
38
38
|
DO NOT call team_claim yourself, only agents claim tasks.
|
|
39
39
|
DO NOT proceed to 6d until team_tasks_add succeeds.
|
|
40
40
|
|
|
41
|
-
**Step 6d.**
|
|
41
|
+
**Step 6d.** Discover relevant skills, then spawn specialists.
|
|
42
|
+
|
|
43
|
+
Before spawning, scan \`.agents/skills/\` and read each \`SKILL.md\` description line.
|
|
44
|
+
Match skills to agents by domain:
|
|
45
|
+
- front-engineer: UI, components, framework skills (e.g. next-best-practices, browser-automation)
|
|
46
|
+
- back-engineer: API, data, service skills
|
|
47
|
+
- infra-engineer: cloud, pipeline, deployment skills
|
|
48
|
+
- quality-engineer: testing, coverage skills
|
|
42
49
|
|
|
43
50
|
Each team_spawn MUST include the agent field (required, causes NOT NULL error if omitted).
|
|
44
51
|
|
|
@@ -49,8 +56,9 @@ const ENSEMBLE_SECTION = `6. **Implement via ensemble team**
|
|
|
49
56
|
4. The 6 OpenCode tools they have available (these are OpenCode tools, NOT shell commands, call them directly as tools, never via bash):
|
|
50
57
|
team_claim, team_tasks_complete, team_tasks_list, team_tasks_add, team_message, team_broadcast
|
|
51
58
|
5. How to proceed: call team_claim tool with the task_id to claim a task before starting it, call team_tasks_complete tool after finishing it, repeat until all their tasks are done, then call team_message tool to notify lead with results or blockers
|
|
59
|
+
6. Which skills to load: list the skill names and paths they MUST read before implementing. Example: "Before starting, read \`.agents/skills/next-best-practices/SKILL.md\` and follow its rules for all Next.js code."
|
|
52
60
|
|
|
53
|
-
Keep spawn prompts under
|
|
61
|
+
Keep spawn prompts under 600 tokens. Do not describe team internals or how ensemble works.
|
|
54
62
|
Only spawn agents whose tasks are actually needed by this change. Skip agents with no tasks.
|
|
55
63
|
|
|
56
64
|
First spawn all agents (wait for each team_spawn to confirm before the next):
|
|
@@ -65,9 +73,9 @@ const ENSEMBLE_SECTION = `6. **Implement via ensemble team**
|
|
|
65
73
|
|
|
66
74
|
Then immediately send each spawned agent a start message to kick them off:
|
|
67
75
|
\`\`\`
|
|
68
|
-
team_message to:"back" text:"Start now.
|
|
69
|
-
team_message to:"front" text:"Start now.
|
|
70
|
-
team_message to:"infra" text:"Start now.
|
|
76
|
+
team_message to:"back" text:"Start now. Read all skills listed in your prompt first, confirm loaded skills, then claim your first task with team_claim."
|
|
77
|
+
team_message to:"front" text:"Start now. Read all skills listed in your prompt first, confirm loaded skills, then claim your first task with team_claim."
|
|
78
|
+
team_message to:"infra" text:"Start now. Read all skills listed in your prompt first, confirm loaded skills, then claim your first task with team_claim."
|
|
71
79
|
\`\`\`
|
|
72
80
|
|
|
73
81
|
**Step 6e.** After sending start messages, tell the user what is running, then STOP and wait.
|
|
@@ -89,7 +97,7 @@ const ENSEMBLE_SECTION = `6. **Implement via ensemble team**
|
|
|
89
97
|
|
|
90
98
|
Spawn quality engineer with worktree:false (read-only, no file edits):
|
|
91
99
|
\`\`\`
|
|
92
|
-
team_spawn name:"quality" agent:"quality-engineer" worktree:false prompt:"<
|
|
100
|
+
team_spawn name:"quality" agent:"quality-engineer" worktree:false prompt:"<verification scope, context summary, run tests + build + lint + verify acceptance criteria, no task claiming required in this phase, send results to lead when done>"
|
|
93
101
|
\`\`\`
|
|
94
102
|
Wait for message -> team_results -> fix blockers -> team_shutdown (no team_merge needed, worktree:false)
|
|
95
103
|
|