opencode-onboard 0.2.6 → 0.2.12
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.md +30 -6
- package/content/.opencode/plugins/session-log.js +266 -35
- package/content/AGENTS.md +384 -384
- package/package.json +1 -1
- package/src/index.js +180 -18
- package/src/steps/__tests__/check-rtk.test.js +3 -2
- package/src/steps/__tests__/clean-ai-files.test.js +1 -1
- package/src/steps/__tests__/copy-content.test.js +4 -2
- package/src/steps/__tests__/token-optimization.test.js +78 -0
- package/src/steps/check-rtk.js +22 -4
- package/src/steps/choose-source-scope.js +1 -1
- package/src/steps/enable-caveman-guidance.js +93 -0
- package/src/steps/install-caveman.js +42 -0
- package/src/steps/install-quota.js +82 -0
- package/src/steps/token-optimization.js +59 -0
- package/src/steps/write-onboard-config.js +2 -0
- package/src/utils/exec.js +19 -7
package/README.md
CHANGED
|
@@ -31,13 +31,36 @@ Most codebases have no `AGENTS.md`, no architecture docs agents can read, and no
|
|
|
31
31
|
npx opencode-onboard@latest
|
|
32
32
|
```
|
|
33
33
|
|
|
34
|
-
Requires **Node.js 18+**.
|
|
34
|
+
Requires **Node.js 18+**.
|
|
35
|
+
|
|
36
|
+
### Run specific steps
|
|
37
|
+
|
|
38
|
+
You can also run individual maintenance/setup steps without the full wizard:
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
# Run one step directly
|
|
42
|
+
npx opencode-onboard clean
|
|
43
|
+
npx opencode-onboard platform
|
|
44
|
+
npx opencode-onboard copy
|
|
45
|
+
npx opencode-onboard openspec
|
|
46
|
+
npx opencode-onboard skills
|
|
47
|
+
npx opencode-onboard models
|
|
48
|
+
npx opencode-onboard optimization
|
|
49
|
+
npx opencode-onboard browser
|
|
50
|
+
npx opencode-onboard metadata
|
|
51
|
+
|
|
52
|
+
# Show CLI help and all commands
|
|
53
|
+
npx opencode-onboard --help
|
|
54
|
+
npx opencode-onboard -h
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
When available, step commands reuse context from `.opencode/opencode-onboard.json`.
|
|
35
58
|
|
|
36
59
|
---
|
|
37
60
|
|
|
38
61
|
## How it works
|
|
39
62
|
|
|
40
|
-
The CLI clears the screen, shows a welcome banner, and walks you through
|
|
63
|
+
The CLI clears the screen, shows a welcome banner, and walks you through 12 steps. The screen always shows the last 2 completed steps + the current one so you always know where you are.
|
|
41
64
|
|
|
42
65
|
| Step | What happens |
|
|
43
66
|
|------|-------------|
|
|
@@ -48,9 +71,10 @@ The CLI clears the screen, shows a welcome banner, and walks you through 10 step
|
|
|
48
71
|
| **5. Copy scaffolding** | Drops agents, skills, and bootstrap docs into your project |
|
|
49
72
|
| **6. Init OpenSpec** | Runs `npx @fission-ai/openspec init` silently for structured change management |
|
|
50
73
|
| **7. Install skills** | Installs built-in `ob-` skills + optional additional skills provider |
|
|
51
|
-
| **8. Choose models** | Fetches live model list from [models.dev](https://models.dev), lets you pick plan / build / fast models with cost indicators and canonical pricing |
|
|
52
|
-
| **9.
|
|
53
|
-
| **
|
|
74
|
+
| **8. Choose models** | Fetches live model list from [models.dev](https://models.dev), lets you pick plan / build / fast models with cost indicators and canonical pricing |
|
|
75
|
+
| **9. Token optimization tools** | Optional (recommended). One checklist step for RTK check, opencode-quota setup, and caveman install (all preselected) |
|
|
76
|
+
| **11. Install browser plugin** | Installs `@different-ai/opencode-browser` globally for agent browser automation |
|
|
77
|
+
| **12. Write onboarding metadata** | Writes `.opencode/opencode-onboard.json` with selected setup details |
|
|
54
78
|
|
|
55
79
|
When it finishes, open OpenCode in your project and type:
|
|
56
80
|
|
|
@@ -181,7 +205,7 @@ After this, every agent has accurate, persistent context about your project, no
|
|
|
181
205
|
| **Node.js 18+** | Required |
|
|
182
206
|
| **[OpenCode](https://opencode.ai)** | The agent runtime |
|
|
183
207
|
| **[OpenCode Ensemble](https://github.com/hueyexe/opencode-ensemble)** | Multi-agent parallel execution |
|
|
184
|
-
| **[rtk](https://github.com/rtk-ai/rtk#pre-built-binaries)** |
|
|
208
|
+
| **[rtk](https://github.com/rtk-ai/rtk#pre-built-binaries)** | Recommended for safer agent CLI command execution |
|
|
185
209
|
| **[gh CLI](https://cli.github.com)** | GitHub platform, must be authenticated |
|
|
186
210
|
| **[az CLI](https://learn.microsoft.com/en-us/cli/azure/install-azure-cli)** + azure-devops extension | Azure DevOps platform |
|
|
187
211
|
|
|
@@ -2,14 +2,29 @@ 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 = 15000
|
|
6
|
+
const DEBUG = process.env.SESSION_LOG_DEBUG === "true"
|
|
5
7
|
|
|
6
|
-
// Per-session state
|
|
8
|
+
// Per-session state
|
|
7
9
|
const sessionState = new Map()
|
|
8
10
|
|
|
11
|
+
// Lead session -> current team name
|
|
12
|
+
const leadTeamBySession = new Map()
|
|
13
|
+
|
|
14
|
+
// Pending spawn records waiting for a session.created match
|
|
15
|
+
const pendingSpawns = []
|
|
16
|
+
|
|
17
|
+
// Team -> completed session snapshots
|
|
18
|
+
const completedByTeam = new Map()
|
|
19
|
+
|
|
9
20
|
function ts() {
|
|
10
21
|
return new Date().toISOString()
|
|
11
22
|
}
|
|
12
23
|
|
|
24
|
+
function nowMs() {
|
|
25
|
+
return Date.now()
|
|
26
|
+
}
|
|
27
|
+
|
|
13
28
|
function appendEntry(directory, entry) {
|
|
14
29
|
const logPath = path.join(directory, LOG_FILE)
|
|
15
30
|
const dir = path.dirname(logPath)
|
|
@@ -40,35 +55,148 @@ function addSkillToState(state, skillName) {
|
|
|
40
55
|
return true
|
|
41
56
|
}
|
|
42
57
|
|
|
43
|
-
function
|
|
58
|
+
function toNum(v) {
|
|
59
|
+
if (typeof v === "number" && Number.isFinite(v)) return v
|
|
60
|
+
if (typeof v === "string" && v.trim() !== "" && !Number.isNaN(Number(v))) return Number(v)
|
|
61
|
+
return null
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function extractReportedTokens(obj) {
|
|
65
|
+
const out = { input: 0, output: 0, total: 0, found: false }
|
|
66
|
+
const visited = new Set()
|
|
67
|
+
|
|
68
|
+
function walk(node) {
|
|
69
|
+
if (!node || typeof node !== "object") return
|
|
70
|
+
if (visited.has(node)) return
|
|
71
|
+
visited.add(node)
|
|
72
|
+
|
|
73
|
+
for (const [k, v] of Object.entries(node)) {
|
|
74
|
+
const key = String(k).toLowerCase()
|
|
75
|
+
const n = toNum(v)
|
|
76
|
+
|
|
77
|
+
if (n !== null) {
|
|
78
|
+
if ((key.includes("input") || key.includes("prompt")) && key.includes("token")) {
|
|
79
|
+
out.input += n
|
|
80
|
+
out.found = true
|
|
81
|
+
} else if ((key.includes("output") || key.includes("completion")) && key.includes("token")) {
|
|
82
|
+
out.output += n
|
|
83
|
+
out.found = true
|
|
84
|
+
} else if (key.includes("total") && key.includes("token")) {
|
|
85
|
+
out.total += n
|
|
86
|
+
out.found = true
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
if (v && typeof v === "object") walk(v)
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
walk(obj)
|
|
95
|
+
return out
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function estimateTokens(state) {
|
|
99
|
+
const inTokens = Math.ceil((state.charIn || 0) / 4)
|
|
100
|
+
const outTokens = Math.ceil((state.charOut || 0) / 4)
|
|
101
|
+
const base = inTokens + outTokens + Math.max(0, (state.toolCalls || 0) * 20)
|
|
102
|
+
const low = Math.max(0, Math.floor(base * 0.7))
|
|
103
|
+
const high = Math.max(low, Math.ceil(base * 1.4))
|
|
104
|
+
return { low, high }
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function usagePayload(state) {
|
|
108
|
+
const est = estimateTokens(state)
|
|
109
|
+
const reportedIn = state.reportedInputTokens || 0
|
|
110
|
+
const reportedOut = state.reportedOutputTokens || 0
|
|
111
|
+
const reportedTotalDirect = state.reportedTotalTokens || 0
|
|
112
|
+
const reportedTotalDerived = reportedIn + reportedOut
|
|
113
|
+
const reportedTotal = reportedTotalDirect || reportedTotalDerived || 0
|
|
114
|
+
|
|
115
|
+
let method = "heuristic"
|
|
116
|
+
if (reportedTotal > 0 && (est.low > 0 || est.high > 0)) method = "mixed"
|
|
117
|
+
else if (reportedTotal > 0) method = "reported"
|
|
118
|
+
|
|
119
|
+
return {
|
|
120
|
+
inputTokensReported: reportedIn || null,
|
|
121
|
+
outputTokensReported: reportedOut || null,
|
|
122
|
+
totalTokensReported: reportedTotal || null,
|
|
123
|
+
tokenEstimateLow: est.low,
|
|
124
|
+
tokenEstimateHigh: est.high,
|
|
125
|
+
method,
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function buildCompletedSnapshot(state, sessionId) {
|
|
130
|
+
return {
|
|
131
|
+
sessionId,
|
|
132
|
+
agent: state.agentName,
|
|
133
|
+
member: state.member || null,
|
|
134
|
+
agentType: state.agentType || null,
|
|
135
|
+
team: state.teamName || null,
|
|
136
|
+
skills: Array.from(state.skills || []).sort(),
|
|
137
|
+
usage: usagePayload(state),
|
|
138
|
+
filesEdited: state.editCount || 0,
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function buildTeamSkillsSummary(teamName) {
|
|
143
|
+
const rows = completedByTeam.get(teamName) || []
|
|
44
144
|
const byAgent = {}
|
|
45
|
-
for (const
|
|
46
|
-
|
|
47
|
-
if (!byAgent[
|
|
48
|
-
for (const
|
|
145
|
+
for (const row of rows) {
|
|
146
|
+
const key = row.member || row.agent || "unknown"
|
|
147
|
+
if (!byAgent[key]) byAgent[key] = { agentType: row.agentType || null, skills: new Set() }
|
|
148
|
+
for (const s of row.skills || []) byAgent[key].skills.add(s)
|
|
49
149
|
}
|
|
150
|
+
const out = {}
|
|
151
|
+
for (const [k, v] of Object.entries(byAgent)) {
|
|
152
|
+
out[k] = {
|
|
153
|
+
agentType: v.agentType,
|
|
154
|
+
skills: Array.from(v.skills).sort(),
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
return out
|
|
158
|
+
}
|
|
50
159
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
160
|
+
function trackCompletedByTeam(snapshot) {
|
|
161
|
+
if (!snapshot.team) return
|
|
162
|
+
if (!completedByTeam.has(snapshot.team)) completedByTeam.set(snapshot.team, [])
|
|
163
|
+
completedByTeam.get(snapshot.team).push(snapshot)
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function enqueuePendingSpawn(leadSessionId, args) {
|
|
167
|
+
pendingSpawns.push({
|
|
168
|
+
leadSessionId,
|
|
169
|
+
at: nowMs(),
|
|
170
|
+
member: args?.name || null,
|
|
171
|
+
agentType: args?.agent || null,
|
|
172
|
+
teamName: leadTeamBySession.get(leadSessionId) || null,
|
|
173
|
+
})
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
function matchPendingSpawn() {
|
|
177
|
+
const now = nowMs()
|
|
178
|
+
// Drop expired pending spawns first
|
|
179
|
+
for (let i = pendingSpawns.length - 1; i >= 0; i--) {
|
|
180
|
+
if (now - pendingSpawns[i].at > SPAWN_MATCH_WINDOW_MS) pendingSpawns.splice(i, 1)
|
|
54
181
|
}
|
|
55
|
-
return
|
|
182
|
+
if (pendingSpawns.length === 0) return null
|
|
183
|
+
return pendingSpawns.shift()
|
|
56
184
|
}
|
|
57
185
|
|
|
58
|
-
// Maps ensemble tool name
|
|
186
|
+
// Maps ensemble tool name -> function that extracts log entry fields from args
|
|
59
187
|
const ENSEMBLE_TOOL_HANDLERS = {
|
|
60
|
-
team_create: (args) => ({ action: "team-created",
|
|
61
|
-
team_spawn: (args) => ({ action: "teammate-spawned",
|
|
62
|
-
team_shutdown: (args) => ({ action: "teammate-shutdown",
|
|
63
|
-
team_merge: (args) => ({ action: "teammate-merged",
|
|
64
|
-
team_cleanup: ()
|
|
65
|
-
team_status: ()
|
|
66
|
-
team_results: (args) => ({ action: "team-results-read",
|
|
67
|
-
team_message: (args) => ({ action: "team-message",
|
|
68
|
-
team_broadcast: (args) => ({ action: "team-broadcast",
|
|
69
|
-
team_tasks_add: (args) => ({ action: "tasks-added",
|
|
70
|
-
team_tasks_complete: (args) => ({ action: "task-completed",
|
|
71
|
-
team_claim: (args) => ({ action: "task-claimed",
|
|
188
|
+
team_create: (args) => ({ action: "team-created", team: args.name }),
|
|
189
|
+
team_spawn: (args) => ({ action: "teammate-spawned", name: args.name, agentType: args.agent }),
|
|
190
|
+
team_shutdown: (args) => ({ action: "teammate-shutdown", name: args.name }),
|
|
191
|
+
team_merge: (args) => ({ action: "teammate-merged", name: args.name }),
|
|
192
|
+
team_cleanup: () => ({ action: "team-cleanup" }),
|
|
193
|
+
team_status: () => ({ action: "team-status-checked" }),
|
|
194
|
+
team_results: (args) => ({ action: "team-results-read", from: args.from }),
|
|
195
|
+
team_message: (args) => ({ action: "team-message", to: args.to ?? "lead", preview: String(args.text ?? "").slice(0, 120) }),
|
|
196
|
+
team_broadcast: (args) => ({ action: "team-broadcast", preview: String(args.text ?? "").slice(0, 120) }),
|
|
197
|
+
team_tasks_add: (args) => ({ action: "tasks-added", count: Array.isArray(args.tasks) ? args.tasks.length : "?" }),
|
|
198
|
+
team_tasks_complete: (args) => ({ action: "task-completed", taskId: args.task_id }),
|
|
199
|
+
team_claim: (args) => ({ action: "task-claimed", taskId: args.task_id }),
|
|
72
200
|
}
|
|
73
201
|
|
|
74
202
|
export const SessionLogPlugin = async ({ client, directory }) => {
|
|
@@ -81,10 +209,35 @@ export const SessionLogPlugin = async ({ client, directory }) => {
|
|
|
81
209
|
|
|
82
210
|
const res = await client.session.get({ path: { id: sessionId } })
|
|
83
211
|
const session = res?.data
|
|
84
|
-
const
|
|
212
|
+
const fallbackAgent = resolveAgentName(session)
|
|
213
|
+
const spawnMatch = matchPendingSpawn()
|
|
214
|
+
|
|
215
|
+
const state = {
|
|
216
|
+
agentName: spawnMatch?.member || fallbackAgent,
|
|
217
|
+
member: spawnMatch?.member || null,
|
|
218
|
+
agentType: spawnMatch?.agentType || null,
|
|
219
|
+
teamName: spawnMatch?.teamName || null,
|
|
220
|
+
editCount: 0,
|
|
221
|
+
skills: new Set(),
|
|
222
|
+
startedAtMs: nowMs(),
|
|
223
|
+
toolCalls: 0,
|
|
224
|
+
charIn: 0,
|
|
225
|
+
charOut: 0,
|
|
226
|
+
reportedInputTokens: 0,
|
|
227
|
+
reportedOutputTokens: 0,
|
|
228
|
+
reportedTotalTokens: 0,
|
|
229
|
+
}
|
|
85
230
|
|
|
86
|
-
sessionState.set(sessionId,
|
|
87
|
-
appendEntry(directory, {
|
|
231
|
+
sessionState.set(sessionId, state)
|
|
232
|
+
appendEntry(directory, {
|
|
233
|
+
ts: ts(),
|
|
234
|
+
agent: state.agentName,
|
|
235
|
+
member: state.member,
|
|
236
|
+
agentType: state.agentType,
|
|
237
|
+
team: state.teamName,
|
|
238
|
+
action: "started",
|
|
239
|
+
sessionId,
|
|
240
|
+
})
|
|
88
241
|
}
|
|
89
242
|
|
|
90
243
|
if (event?.type === "file.edited") {
|
|
@@ -100,9 +253,21 @@ export const SessionLogPlugin = async ({ client, directory }) => {
|
|
|
100
253
|
const state = sessionState.get(sessionId)
|
|
101
254
|
if (!state) return
|
|
102
255
|
|
|
103
|
-
const
|
|
104
|
-
const
|
|
105
|
-
appendEntry(directory, {
|
|
256
|
+
const skills = Array.from(state.skills || []).sort()
|
|
257
|
+
const usage = usagePayload(state)
|
|
258
|
+
appendEntry(directory, {
|
|
259
|
+
ts: ts(),
|
|
260
|
+
agent: state.agentName,
|
|
261
|
+
member: state.member,
|
|
262
|
+
agentType: state.agentType,
|
|
263
|
+
team: state.teamName,
|
|
264
|
+
action: "completed",
|
|
265
|
+
filesEdited: state.editCount,
|
|
266
|
+
skills,
|
|
267
|
+
usage,
|
|
268
|
+
})
|
|
269
|
+
|
|
270
|
+
trackCompletedByTeam(buildCompletedSnapshot(state, sessionId))
|
|
106
271
|
sessionState.delete(sessionId)
|
|
107
272
|
}
|
|
108
273
|
} catch (_) {}
|
|
@@ -117,13 +282,49 @@ export const SessionLogPlugin = async ({ client, directory }) => {
|
|
|
117
282
|
if (!state) return
|
|
118
283
|
|
|
119
284
|
const tool = input?.tool
|
|
285
|
+
const args = input?.args ?? {}
|
|
286
|
+
|
|
287
|
+
state.toolCalls++
|
|
288
|
+
state.charIn += JSON.stringify(args).length
|
|
289
|
+
state.charOut += JSON.stringify(output ?? {}).length
|
|
290
|
+
|
|
291
|
+
const reportedIn = extractReportedTokens(input)
|
|
292
|
+
const reportedOut = extractReportedTokens(output)
|
|
293
|
+
if (reportedIn.found) {
|
|
294
|
+
state.reportedInputTokens += reportedIn.input
|
|
295
|
+
state.reportedOutputTokens += reportedIn.output
|
|
296
|
+
state.reportedTotalTokens += reportedIn.total
|
|
297
|
+
}
|
|
298
|
+
if (reportedOut.found) {
|
|
299
|
+
state.reportedInputTokens += reportedOut.input
|
|
300
|
+
state.reportedOutputTokens += reportedOut.output
|
|
301
|
+
state.reportedTotalTokens += reportedOut.total
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
if (DEBUG && !reportedIn.found && !reportedOut.found && tool !== "read") {
|
|
305
|
+
appendEntry(directory, {
|
|
306
|
+
ts: ts(),
|
|
307
|
+
agent: state.agentName,
|
|
308
|
+
action: "debug-no-token-metrics",
|
|
309
|
+
tool,
|
|
310
|
+
})
|
|
311
|
+
}
|
|
120
312
|
|
|
121
313
|
// Track skill loads via skill tool (primary)
|
|
122
314
|
if (tool === "skill") {
|
|
123
315
|
const skillName = input?.args?.name
|
|
124
316
|
const added = addSkillToState(state, skillName)
|
|
125
317
|
if (added) {
|
|
126
|
-
appendEntry(directory, {
|
|
318
|
+
appendEntry(directory, {
|
|
319
|
+
ts: ts(),
|
|
320
|
+
agent: state.agentName,
|
|
321
|
+
member: state.member,
|
|
322
|
+
agentType: state.agentType,
|
|
323
|
+
team: state.teamName,
|
|
324
|
+
action: "skill-loaded",
|
|
325
|
+
skill: skillName,
|
|
326
|
+
source: "skill-tool",
|
|
327
|
+
})
|
|
127
328
|
}
|
|
128
329
|
return
|
|
129
330
|
}
|
|
@@ -136,23 +337,53 @@ export const SessionLogPlugin = async ({ client, directory }) => {
|
|
|
136
337
|
const skillName = match[1]
|
|
137
338
|
const added = addSkillToState(state, skillName)
|
|
138
339
|
if (added) {
|
|
139
|
-
appendEntry(directory, {
|
|
340
|
+
appendEntry(directory, {
|
|
341
|
+
ts: ts(),
|
|
342
|
+
agent: state.agentName,
|
|
343
|
+
member: state.member,
|
|
344
|
+
agentType: state.agentType,
|
|
345
|
+
team: state.teamName,
|
|
346
|
+
action: "skill-loaded",
|
|
347
|
+
skill: skillName,
|
|
348
|
+
source: "read-skill-file",
|
|
349
|
+
})
|
|
140
350
|
}
|
|
141
351
|
}
|
|
142
352
|
return
|
|
143
353
|
}
|
|
144
354
|
|
|
145
|
-
const args = input?.args ?? {}
|
|
146
|
-
|
|
147
355
|
// Track ensemble tool calls
|
|
148
356
|
const ensembleHandler = ENSEMBLE_TOOL_HANDLERS[tool]
|
|
149
357
|
if (!ensembleHandler) return
|
|
150
358
|
|
|
151
|
-
const entry = {
|
|
359
|
+
const entry = {
|
|
360
|
+
ts: ts(),
|
|
361
|
+
agent: state.agentName,
|
|
362
|
+
member: state.member,
|
|
363
|
+
agentType: state.agentType,
|
|
364
|
+
team: state.teamName,
|
|
365
|
+
...ensembleHandler(args),
|
|
366
|
+
}
|
|
152
367
|
appendEntry(directory, entry)
|
|
153
368
|
|
|
369
|
+
if (tool === "team_create") {
|
|
370
|
+
leadTeamBySession.set(sessionId, args?.name || null)
|
|
371
|
+
state.teamName = args?.name || state.teamName
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
if (tool === "team_spawn") {
|
|
375
|
+
enqueuePendingSpawn(sessionId, args)
|
|
376
|
+
}
|
|
377
|
+
|
|
154
378
|
if (tool === "team_cleanup") {
|
|
155
|
-
|
|
379
|
+
const teamName = state.teamName || leadTeamBySession.get(sessionId)
|
|
380
|
+
appendEntry(directory, {
|
|
381
|
+
ts: ts(),
|
|
382
|
+
agent: state.agentName,
|
|
383
|
+
action: "team-skills-summary",
|
|
384
|
+
team: teamName || null,
|
|
385
|
+
byAgent: teamName ? buildTeamSkillsSummary(teamName) : {},
|
|
386
|
+
})
|
|
156
387
|
}
|
|
157
388
|
} catch (_) {}
|
|
158
389
|
},
|