wendkeep 0.11.0 → 0.12.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
@@ -4,6 +4,18 @@ All notable changes to **wendkeep** are documented here. Format based on
4
4
  [Keep a Changelog](https://keepachangelog.com/en/1.1.0/); this project follows
5
5
  [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6
6
 
7
+ ## [0.12.0] — 2026-07-06
8
+
9
+ Deeper subagent/workflow telemetry.
10
+
11
+ ### Added
12
+ - **Workflow run metadata** in the `## Subagents & Workflows` section: each run now shows its
13
+ **status** (completed / killed / …), phase titles, duration and agent count — read from the
14
+ authoritative `workflows/wf_*.json`. On a real session this surfaced a **killed** run that
15
+ still cost $2.50 next to the completed $5.76 one — wasted spend you couldn't see before.
16
+ - **Subagent tools rollup:** the distinct tools the subagents used, shown in the section and a
17
+ new `subagents_tools` frontmatter field.
18
+
7
19
  ## [0.11.0] — 2026-07-06
8
20
 
9
21
  Vault-wide cost.
@@ -249,6 +261,7 @@ Initial release — the capture engine, extracted from a system in daily product
249
261
  - `wendkeep init` (cross-platform installer) + optional `@bitbonsai/mcpvault` MCP server.
250
262
 
251
263
  <!-- Only v0.4.0+ is tagged in git (history starts here); older versions link to npm. -->
264
+ [0.12.0]: https://github.com/rogersialves/wendkeep/releases/tag/v0.12.0
252
265
  [0.11.0]: https://github.com/rogersialves/wendkeep/releases/tag/v0.11.0
253
266
  [0.10.0]: https://github.com/rogersialves/wendkeep/releases/tag/v0.10.0
254
267
  [0.9.1]: https://github.com/rogersialves/wendkeep/releases/tag/v0.9.1
@@ -45,6 +45,29 @@ function runIdOfPath(file) {
45
45
  return m ? m[1] : null;
46
46
  }
47
47
 
48
+ // workflows/wf_<rid>.json carries authoritative run metadata (name/status/phases/duration).
49
+ function readWorkflowRuns(sessionDir) {
50
+ const runs = {};
51
+ let names;
52
+ try { names = readdirSync(join(sessionDir, 'workflows')); } catch { return runs; }
53
+ for (const n of names) {
54
+ if (!/^wf_.*\.json$/i.test(n)) continue;
55
+ try {
56
+ const o = JSON.parse(readFileSync(join(sessionDir, 'workflows', n), 'utf8'));
57
+ const runId = o.runId || n.replace(/\.json$/i, '');
58
+ runs[runId] = {
59
+ name: o.workflowName || runId,
60
+ status: o.status || '',
61
+ agentCount: o.agentCount || 0,
62
+ totalTokens: o.totalTokens || 0,
63
+ durationMs: o.durationMs || 0,
64
+ phases: (o.phases || []).map((p) => p && p.title).filter(Boolean),
65
+ };
66
+ } catch { /* skip bad run json */ }
67
+ }
68
+ return runs;
69
+ }
70
+
48
71
  function tokensTotal(t = {}) {
49
72
  return (t.input || 0) + (t.cached || 0) + (t.cacheWrite || 0) + (t.output || 0);
50
73
  }
@@ -60,8 +83,10 @@ export function collectSubagentUsage(sessionDir) {
60
83
  if (!files.length) return null;
61
84
 
62
85
  const nameMap = workflowNameMap(sessionDir);
86
+ const runs = readWorkflowRuns(sessionDir);
63
87
  const subagents = [];
64
88
  const wf = {};
89
+ const allTools = new Set();
65
90
  const usageAgg = { input: 0, cached: 0, cacheWrite: 0, output: 0 };
66
91
  let count = 0;
67
92
  let calls = 0;
@@ -71,10 +96,11 @@ export function collectSubagentUsage(sessionDir) {
71
96
  const summary = summarizeTokenUsage(parseTokenUsageFromTranscript(f));
72
97
  if (!summary.calls) continue;
73
98
  const runId = runIdOfPath(f);
74
- const workflow = runId ? nameMap[runId] || runId : null;
99
+ const workflow = runId ? (runs[runId] && runs[runId].name) || nameMap[runId] || runId : null;
75
100
  let agentType = '';
76
101
  try { agentType = JSON.parse(readFileSync(f.replace(/\.jsonl$/i, '.meta.json'), 'utf8')).agentType || ''; } catch { /* no meta */ }
77
102
  const tokens = tokensTotal(summary.totals);
103
+ for (const t of summary.tools) allTools.add(t);
78
104
 
79
105
  subagents.push({
80
106
  id: basename(f).replace(/^agent-/, '').replace(/\.jsonl$/i, '').slice(0, 12),
@@ -82,6 +108,7 @@ export function collectSubagentUsage(sessionDir) {
82
108
  workflow,
83
109
  model: summary.models[0] || '?',
84
110
  tools: summary.tools.length,
111
+ toolNames: summary.tools,
85
112
  calls: summary.calls,
86
113
  tokens,
87
114
  cost: round4(summary.costs.model),
@@ -99,22 +126,44 @@ export function collectSubagentUsage(sessionDir) {
99
126
  }
100
127
  if (!count) return null;
101
128
 
102
- const workflows = Object.entries(wf).map(([runId, v]) => ({
103
- runId, name: nameMap[runId] || runId, agents: v.agents, cost: round4(v.cost),
104
- }));
129
+ // Merge transcript-derived cost with the authoritative run metadata (status/phases/duration).
130
+ const runIds = new Set([...Object.keys(wf), ...Object.keys(runs)]);
131
+ const workflows = [...runIds].map((runId) => {
132
+ const t = wf[runId] || { agents: 0, cost: 0 };
133
+ const r = runs[runId] || {};
134
+ return {
135
+ runId,
136
+ name: r.name || nameMap[runId] || runId,
137
+ status: r.status || '',
138
+ agents: r.agentCount || t.agents,
139
+ cost: round4(t.cost),
140
+ totalTokens: r.totalTokens || 0,
141
+ durationMs: r.durationMs || 0,
142
+ phases: r.phases || [],
143
+ };
144
+ });
105
145
 
106
146
  return {
107
147
  subagents,
108
148
  workflows,
109
- aggregate: { count, calls, tokens: tokensTotal(usageAgg), cost: round4(cost), usage: usageAgg },
149
+ aggregate: { count, calls, tokens: tokensTotal(usageAgg), cost: round4(cost), usage: usageAgg, tools: [...allTools] },
110
150
  };
111
151
  }
112
152
 
153
+ function workflowLine(w) {
154
+ const parts = [w.runId];
155
+ if (w.status) parts.push(w.status);
156
+ parts.push(`${w.agents} agentes`);
157
+ if (w.phases && w.phases.length) parts.push(`fases: ${w.phases.join(', ')}`);
158
+ if (w.durationMs) parts.push(`${Math.round(w.durationMs / 1000)}s`);
159
+ parts.push(usd(w.cost));
160
+ return `${w.name} (${parts.join(' · ')})`;
161
+ }
162
+
113
163
  export function renderSubagentSection(c) {
114
164
  const a = c.aggregate;
115
- const wf = c.workflows.length
116
- ? c.workflows.map((w) => `${w.name} (${w.runId} · ${w.agents} agentes · ${usd(w.cost)})`).join('; ')
117
- : '(nenhum)';
165
+ const wf = c.workflows.length ? c.workflows.map(workflowLine).join('; ') : '(nenhum)';
166
+ const tools = a.tools && a.tools.length ? a.tools.join(', ') : '(nenhuma)';
118
167
  const rows = c.subagents
119
168
  .map((s) => `| ${s.id} | ${s.agentType || '-'} | ${s.workflow || '-'} | ${s.model} | ${s.tools} | ${fmt(s.tokens)} | ${usd(s.cost)} |`)
120
169
  .join('\n');
@@ -124,6 +173,7 @@ export function renderSubagentSection(c) {
124
173
 
125
174
  - **Subagents:** ${a.count} · ${a.calls} chamadas · ${fmt(a.tokens)} tokens · ${usd(a.cost)}
126
175
  - **Workflows:** ${wf}
176
+ - **Tools (subagents):** ${tools}
127
177
 
128
178
  <details><summary>Por subagent (${a.count})</summary>
129
179
 
@@ -169,6 +219,7 @@ export function upsertSubagentUsage(sessionPath, transcriptPath) {
169
219
  content = setFrontmatterField(content, 'subagents_count', a.count);
170
220
  content = setFrontmatterField(content, 'subagents_tokens_total', a.tokens);
171
221
  content = setFrontmatterField(content, 'subagents_custo_usd', a.cost);
222
+ content = setFrontmatterField(content, 'subagents_tools', `"${(a.tools || []).join(', ')}"`);
172
223
  content = setFrontmatterField(content, 'tokens_total_incl_subagents', frontmatterNumber(content, 'tokens_total') + a.tokens);
173
224
  content = upsertSection(content, '## Subagents & Workflows', renderSubagentSection(collected));
174
225
  writeFileSync(sessionPath, content, 'utf8');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wendkeep",
3
- "version": "0.11.0",
3
+ "version": "0.12.0",
4
4
  "description": "A persistent-memory harness for AI coding agents on your Obsidian vault: turn-by-turn session capture plus a native, zero-dependency spec→change→verify→archive loop (sensor-gated, independent verdict, mutation discrimination). Local-first, agent-agnostic (Claude Code, Codex, Cursor…).",
5
5
  "type": "module",
6
6
  "bin": {