@claude-flow/cli 3.63.6 → 3.63.7

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.
Files changed (39) hide show
  1. package/.claude/agents/github/github-modes.md +3 -3
  2. package/.claude/agents/github/issue-tracker.md +6 -6
  3. package/.claude/agents/github/pr-manager.md +6 -6
  4. package/.claude/agents/github/release-manager.md +11 -11
  5. package/.claude/agents/github/repo-architect.md +9 -9
  6. package/.claude/agents/github/swarm-issue.md +4 -4
  7. package/.claude/agents/github/swarm-pr.md +5 -5
  8. package/.claude/agents/github/sync-coordinator.md +18 -18
  9. package/.claude/agents/github/workflow-automation.md +7 -7
  10. package/.claude/commands/agents/agent-spawning.md +1 -1
  11. package/.claude/commands/agents/spawn.md +1 -1
  12. package/.claude/commands/github/github-modes.md +3 -3
  13. package/.claude/commands/github/issue-tracker.md +6 -6
  14. package/.claude/commands/github/pr-manager.md +6 -6
  15. package/.claude/commands/github/release-manager.md +11 -11
  16. package/.claude/commands/github/repo-architect.md +9 -9
  17. package/.claude/commands/github/sync-coordinator.md +9 -9
  18. package/.claude/commands/sparc/orchestrator.md +1 -1
  19. package/.claude/commands/sparc/sparc-modes.md +1 -1
  20. package/.claude/helpers/helpers.manifest.json +2 -2
  21. package/.claude/skills/github-code-review/SKILL.md +3 -3
  22. package/.claude/skills/github-multi-repo/SKILL.md +6 -9
  23. package/.claude/skills/github-project-management/SKILL.md +7 -7
  24. package/.claude/skills/github-release-management/SKILL.md +12 -14
  25. package/.claude/skills/github-workflow-automation/SKILL.md +7 -7
  26. package/.claude/skills/sparc-methodology/SKILL.md +11 -11
  27. package/.claude/skills/swarm-advanced/SKILL.md +5 -5
  28. package/README.md +1 -1
  29. package/catalog-manifest.json +2 -2
  30. package/dist/src/commands/hooks.js +7 -0
  31. package/dist/src/mcp-tools/agent-tools.js +19 -5
  32. package/dist/src/mcp-tools/coordination-tools.js +10 -12
  33. package/dist/src/mcp-tools/hooks-tools.js +11 -2
  34. package/dist/src/mcp-tools/performance-tools.js +13 -23
  35. package/dist/src/mcp-tools/session-tools.js +25 -8
  36. package/dist/src/missions/schemas.d.ts +113 -113
  37. package/dist/src/mods/command-registry/schema.d.ts +87 -87
  38. package/dist/src/services/worker-daemon.js +11 -4
  39. package/package.json +1 -1
@@ -140,12 +140,12 @@ gh release create $(npm pkg get version) \
140
140
  }
141
141
 
142
142
  // Spawn specialized agents
143
- mcp__claude-flow__agent_spawn { type: "coordinator", name: "Release Director" }
144
- mcp__claude-flow__agent_spawn { type: "coder", name: "Version Manager" }
145
- mcp__claude-flow__agent_spawn { type: "tester", name: "QA Engineer" }
146
- mcp__claude-flow__agent_spawn { type: "reviewer", name: "Release Reviewer" }
147
- mcp__claude-flow__agent_spawn { type: "analyst", name: "Deployment Analyst" }
148
- mcp__claude-flow__agent_spawn { type: "researcher", name: "Compatibility Checker" }
143
+ mcp__claude-flow__agent_spawn { agentType: "coordinator", name: "Release Director" }
144
+ mcp__claude-flow__agent_spawn { agentType: "coder", name: "Version Manager" }
145
+ mcp__claude-flow__agent_spawn { agentType: "tester", name: "QA Engineer" }
146
+ mcp__claude-flow__agent_spawn { agentType: "reviewer", name: "Release Reviewer" }
147
+ mcp__claude-flow__agent_spawn { agentType: "analyst", name: "Deployment Analyst" }
148
+ mcp__claude-flow__agent_spawn { agentType: "researcher", name: "Compatibility Checker" }
149
149
  ```
150
150
 
151
151
  #### Coordinated Release Workflow
@@ -155,11 +155,10 @@ gh release create $(npm pkg get version) \
155
155
  Bash("gh api repos/:owner/:repo/git/refs --method POST -f ref='refs/heads/release/v2.0.0' -f sha=$(gh api repos/:owner/:repo/git/refs/heads/main --jq '.object.sha')")
156
156
 
157
157
  // Orchestrate release preparation
158
- mcp__claude-flow__task_orchestrate {
159
- task: "Prepare release v2.0.0 with comprehensive testing and validation",
160
- strategy: "sequential",
161
- priority: "critical",
162
- maxAgents: 6
158
+ mcp__claude-flow__task_create {
159
+ type: "release",
160
+ description: "Prepare release v2.0.0 with comprehensive testing and validation",
161
+ priority: "critical"
163
162
  }
164
163
 
165
164
  // Update all release files
@@ -187,8 +186,7 @@ gh release create $(npm pkg get version) \
187
186
  ]}
188
187
 
189
188
  // Store release state
190
- mcp__claude-flow__memory_usage {
191
- action: "store",
189
+ mcp__claude-flow__memory_store {
192
190
  key: "release/v2.0.0/status",
193
191
  value: JSON.stringify({
194
192
  version: "2.0.0",
@@ -391,7 +389,7 @@ npx @claude-flow/cli@latest github multi-release \
391
389
  Bash("gh api repos/org/cli/dispatches --method POST -f event_type='release' -F client_payload[version]=v1.5.0")
392
390
 
393
391
  // Monitor all releases
394
- mcp__claude-flow__swarm_monitor { interval: 5, duration: 300 }
392
+ mcp__claude-flow__swarm_status {}
395
393
  ```
396
394
 
397
395
  ### Hotfix Emergency Procedures
@@ -525,15 +525,15 @@ mcp__claude-flow__swarm_init {
525
525
  }
526
526
 
527
527
  // Step 2: Spawn specialized agents
528
- mcp__claude-flow__agent_spawn { type: "coordinator", name: "GitHub Coordinator" }
529
- mcp__claude-flow__agent_spawn { type: "reviewer", name: "Code Reviewer" }
530
- mcp__claude-flow__agent_spawn { type: "tester", name: "QA Agent" }
531
- mcp__claude-flow__agent_spawn { type: "analyst", name: "Security Analyst" }
528
+ mcp__claude-flow__agent_spawn { agentType: "coordinator", name: "GitHub Coordinator" }
529
+ mcp__claude-flow__agent_spawn { agentType: "reviewer", name: "Code Reviewer" }
530
+ mcp__claude-flow__agent_spawn { agentType: "tester", name: "QA Agent" }
531
+ mcp__claude-flow__agent_spawn { agentType: "analyst", name: "Security Analyst" }
532
532
 
533
533
  // Step 3: Orchestrate GitHub workflow
534
- mcp__claude-flow__task_orchestrate {
535
- task: "Complete PR review and merge workflow",
536
- strategy: "parallel",
534
+ mcp__claude-flow__task_create {
535
+ type: "review",
536
+ description: "Complete PR review and merge workflow",
537
537
  priority: "high"
538
538
  }
539
539
  ```
@@ -455,7 +455,7 @@ mcp__claude-flow__swarm_init {
455
455
 
456
456
  // Spawn specialized agents
457
457
  mcp__claude-flow__agent_spawn {
458
- type: "<agent-type>",
458
+ agentType: "<agent-type>",
459
459
  capabilities: ["<capability1>", "<capability2>"]
460
460
  }
461
461
 
@@ -522,15 +522,15 @@ mcp__claude-flow__swarm_init {
522
522
 
523
523
  // Spawn coordinator
524
524
  mcp__claude-flow__agent_spawn {
525
- type: "coordinator",
525
+ agentType: "coordinator",
526
526
  capabilities: ["planning", "delegation", "monitoring"]
527
527
  }
528
528
 
529
529
  // Spawn specialized workers
530
- mcp__claude-flow__agent_spawn { type: "architect" }
531
- mcp__claude-flow__agent_spawn { type: "coder" }
532
- mcp__claude-flow__agent_spawn { type: "tester" }
533
- mcp__claude-flow__agent_spawn { type: "reviewer" }
530
+ mcp__claude-flow__agent_spawn { agentType: "architect" }
531
+ mcp__claude-flow__agent_spawn { agentType: "coder" }
532
+ mcp__claude-flow__agent_spawn { agentType: "tester" }
533
+ mcp__claude-flow__agent_spawn { agentType: "reviewer" }
534
534
  ```
535
535
 
536
536
  ### Pattern 2: Mesh Coordination
@@ -703,14 +703,14 @@ mcp__claude-flow__memory_usage {
703
703
  ```javascript
704
704
  // ✅ CORRECT: All operations together
705
705
  [Single Message]:
706
- mcp__claude-flow__agent_spawn { type: "researcher" }
707
- mcp__claude-flow__agent_spawn { type: "coder" }
708
- mcp__claude-flow__agent_spawn { type: "tester" }
706
+ mcp__claude-flow__agent_spawn { agentType: "researcher" }
707
+ mcp__claude-flow__agent_spawn { agentType: "coder" }
708
+ mcp__claude-flow__agent_spawn { agentType: "tester" }
709
709
  TodoWrite { todos: [8-10 todos] }
710
710
 
711
711
  // ❌ WRONG: Multiple messages
712
- Message 1: mcp__claude-flow__agent_spawn { type: "researcher" }
713
- Message 2: mcp__claude-flow__agent_spawn { type: "coder" }
712
+ Message 1: mcp__claude-flow__agent_spawn { agentType: "researcher" }
713
+ Message 2: mcp__claude-flow__agent_spawn { agentType: "coder" }
714
714
  Message 3: TodoWrite { todos: [...] }
715
715
  ```
716
716
 
@@ -25,7 +25,7 @@ claude mcp add claude-flow npx claude-flow@alpha mcp start
25
25
  mcp__claude-flow__swarm_init({ topology: "mesh", maxAgents: 6 })
26
26
 
27
27
  // 2. Spawn specialized agents
28
- mcp__claude-flow__agent_spawn({ type: "researcher", name: "Agent 1" })
28
+ mcp__claude-flow__agent_spawn({ agentType: "researcher", name: "Agent 1" })
29
29
 
30
30
  // 3. Orchestrate tasks
31
31
  mcp__claude-flow__task_orchestrate({ task: "...", strategy: "parallel" })
@@ -108,7 +108,7 @@ const researchAgents = [
108
108
  // Spawn all agents
109
109
  researchAgents.forEach(agent => {
110
110
  mcp__claude-flow__agent_spawn({
111
- type: agent.type,
111
+ agentType: agent.type,
112
112
  name: agent.name,
113
113
  capabilities: agent.capabilities
114
114
  })
@@ -274,7 +274,7 @@ const devTeam = [
274
274
  // Spawn all team members
275
275
  devTeam.forEach(member => {
276
276
  mcp__claude-flow__agent_spawn({
277
- type: member.type,
277
+ agentType: member.type,
278
278
  name: member.name,
279
279
  capabilities: member.capabilities,
280
280
  swarmId: "dev-swarm"
@@ -444,7 +444,7 @@ const testingTeam = [
444
444
  // Spawn all testers
445
445
  testingTeam.forEach(tester => {
446
446
  mcp__claude-flow__agent_spawn({
447
- type: tester.type,
447
+ agentType: tester.type,
448
448
  name: tester.name,
449
449
  capabilities: tester.capabilities,
450
450
  swarmId: "testing-swarm"
@@ -634,7 +634,7 @@ const analysisTeam = [
634
634
  // Spawn all analysts
635
635
  analysisTeam.forEach(analyst => {
636
636
  mcp__claude-flow__agent_spawn({
637
- type: analyst.type,
637
+ agentType: analyst.type,
638
638
  name: analyst.name,
639
639
  capabilities: analyst.capabilities
640
640
  })
package/README.md CHANGED
@@ -244,7 +244,7 @@ This adds slash commands and agent definitions. `ruflo-core` (installed above) d
244
244
  | [**ruflo-browser**](plugins/ruflo-browser/README.md) | Automate browser testing with Playwright |
245
245
  | [**ruflo-jujutsu**](plugins/ruflo-jujutsu/README.md) | Analyze git diffs, score risk, suggest reviewers |
246
246
  | [**ruflo-docs**](plugins/ruflo-docs/README.md) | Generate and maintain documentation automatically |
247
- | [**agentic-qe-fleet**](https://github.com/proffesor-for-testing/agentic-qe/tree/v3.15.15/plugins/agentic-qe-fleet) | External, maintained by [Agentic QE](https://github.com/proffesor-for-testing/agentic-qe): 11 QE agents, test generation, coverage, chaos and TDD skills, the `agentic-qe` MCP server, and a guard for AQE learning data (`/plugin install agentic-qe-fleet@ruflo`). Install it from one marketplace only, `agentic-qe` or `ruflo`: enabled from both, its mod and commands load twice |
247
+ | [**agentic-qe-fleet**](https://github.com/proffesor-for-testing/agentic-qe/tree/v3.15.16/plugins/agentic-qe-fleet) | External, maintained by [Agentic QE](https://github.com/proffesor-for-testing/agentic-qe): 11 QE agents, test generation, coverage, chaos and TDD skills, the `agentic-qe` MCP server, and a guard for AQE learning data (`/plugin install agentic-qe-fleet@ruflo`). Install it from one marketplace only, `agentic-qe` or `ruflo`: enabled from both, its mod and commands load twice |
248
248
 
249
249
  #### Security & Compliance
250
250
 
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "schemaVersion": 1,
3
3
  "generation": 7,
4
- "generatedAt": "2026-10-27T21:48:29.562Z",
5
- "gitSha": "a4547c04",
4
+ "generatedAt": "2026-10-28T15:08:17.042Z",
5
+ "gitSha": "8935d9a8",
6
6
  "catalog": {
7
7
  "agents": 177,
8
8
  "tools": 444,
@@ -967,6 +967,13 @@ const buildAgentsCommand = {
967
967
  format: configFormat,
968
968
  includePretrained: true,
969
969
  });
970
+ // #4558: the tool now rejects an invalid outputDir with { success: false, error }.
971
+ const failed = result;
972
+ if (failed.success === false) {
973
+ spinner.fail('Agent config generation failed');
974
+ output.printError(`Build agents error: ${failed.error}`);
975
+ return { success: false, exitCode: 1 };
976
+ }
970
977
  spinner.succeed(`Generated ${result.agents.length} agent configs`);
971
978
  if (ctx.flags.format === 'json') {
972
979
  output.printJson(result);
@@ -214,7 +214,7 @@ export const agentTools = [
214
214
  inputSchema: {
215
215
  type: 'object',
216
216
  properties: {
217
- agentType: { type: 'string', description: 'Type of agent to spawn' },
217
+ agentType: { type: 'string', description: 'Type of agent to spawn (a legacy `type` key is accepted as an alias)' },
218
218
  agentId: { type: 'string', description: 'Optional custom agent ID' },
219
219
  name: { type: 'string', description: 'Optional display name for the agent (shown by agent_list, agent_status and ruflo-console)' },
220
220
  // #2085 — accept swarmId so spawned agents register in the
@@ -234,7 +234,19 @@ export const agentTools = [
234
234
  },
235
235
  required: ['agentType'],
236
236
  },
237
- handler: async (input) => {
237
+ handler: async (rawInput) => {
238
+ // #4542: agent and command docs that `init` installed before 3.63.7 call
239
+ // agent_spawn with `type`. Accept it as an alias for `agentType`, and
240
+ // name the missing key ourselves: validateAgentSpawn maps agentType onto
241
+ // the security schema's `type` field and would report "type: Required".
242
+ let input = rawInput;
243
+ if (input.agentType === undefined && typeof input.type === 'string') {
244
+ const { type, ...rest } = input;
245
+ input = { ...rest, agentType: type };
246
+ }
247
+ if (typeof input.agentType !== 'string' || input.agentType === '') {
248
+ return { success: false, error: 'Input validation failed: agentType is required and must be a non-empty string (for example agentType: "coder")' };
249
+ }
238
250
  // Validate user-provided input (#1425: wire security validators to runtime)
239
251
  const validation = await validateAgentSpawn(input);
240
252
  if (!validation.valid) {
@@ -606,7 +618,7 @@ export const agentTools = [
606
618
  type: 'object',
607
619
  properties: {
608
620
  action: { type: 'string', enum: ['status', 'scale', 'drain', 'fill'], description: 'Pool action' },
609
- targetSize: { type: 'number', description: 'Target pool size (for scale action)' },
621
+ targetSize: { type: 'number', description: 'Target pool size (scale: grow or shrink to it; fill: grow to it, never shrink)' },
610
622
  agentType: { type: 'string', description: 'Agent type filter' },
611
623
  },
612
624
  required: ['action'],
@@ -653,7 +665,9 @@ export const agentTools = [
653
665
  avgHealth: agents.length > 0 ? agents.reduce((sum, a) => sum + a.health, 0) / agents.length : 0,
654
666
  };
655
667
  }
656
- if (action === 'scale') {
668
+ // #4555: fill was advertised in the enum but had no branch. It is scale
669
+ // without the shrink step: grow agentType's pool to targetSize, never terminate.
670
+ if (action === 'scale' || action === 'fill') {
657
671
  const targetSize = input.targetSize === undefined ? 5 : input.targetSize;
658
672
  if (typeof targetSize !== 'number' || !Number.isSafeInteger(targetSize) || targetSize < 0) {
659
673
  return { action, error: 'targetSize must be a non-negative safe integer' };
@@ -678,7 +692,7 @@ export const agentTools = [
678
692
  added.push(agentId);
679
693
  }
680
694
  }
681
- else if (delta < 0) {
695
+ else if (delta < 0 && action === 'scale') {
682
696
  const toRemove = agents.filter(a => a.agentType === agentType && a.status === 'idle').slice(0, -delta);
683
697
  for (const agent of toRemove) {
684
698
  store.agents[agent.agentId].status = 'terminated';
@@ -225,8 +225,8 @@ export const coordinationTools = [
225
225
  inputSchema: {
226
226
  type: 'object',
227
227
  properties: {
228
- action: { type: 'string', enum: ['status', 'trigger', 'resolve'], description: 'Action to perform' },
229
- force: { type: 'boolean', description: 'Force synchronization' },
228
+ action: { type: 'string', enum: ['status', 'trigger', 'resolve'], description: 'Action to perform. `trigger` is unavailable: nodes are local records with no sync backend, so it is refused (#4557)' },
229
+ force: { type: 'boolean', description: 'Unused: `trigger` is refused regardless (#4557)' },
230
230
  conflictResolution: { type: 'string', enum: ['latest', 'merge', 'manual'], description: 'Conflict resolution strategy' },
231
231
  },
232
232
  },
@@ -243,18 +243,16 @@ export const coordinationTools = [
243
243
  };
244
244
  }
245
245
  if (action === 'trigger') {
246
- store.sync.syncCount++;
247
- store.sync.lastSync = new Date().toISOString();
248
- store.sync.pendingChanges = 0;
249
- // Simulate sync
250
- await new Promise(resolve => setTimeout(resolve, 50));
251
- saveCoordStore(store);
246
+ // #4557: this waited on a simulated 50ms "sync", bumped syncCount/lastSync
247
+ // and reported every node synchronized, but no state moves between
248
+ // nodes. Refuse without touching the stored sync record, as
249
+ // neural_compress quantize/prune do (#4195, #4423).
252
250
  return {
253
- success: true,
254
- action: 'synchronized',
251
+ success: false,
252
+ action: 'trigger',
253
+ error: 'Synchronization is unavailable: coordination nodes are local records with no sync backend, so nothing would be transferred.',
255
254
  syncCount: store.sync.syncCount,
256
- syncedAt: store.sync.lastSync,
257
- nodesSync: Object.keys(store.nodes).length,
255
+ lastSync: store.sync.lastSync,
258
256
  };
259
257
  }
260
258
  if (action === 'resolve') {
@@ -2212,7 +2212,14 @@ export const hooksBuildAgents = {
2212
2212
  },
2213
2213
  },
2214
2214
  handler: async (params) => {
2215
- const outputDir = resolve(params.outputDir || './agents');
2215
+ // #4558: outputDir was never validated and resolved against the process
2216
+ // cwd, so a default call from another directory overwrote files there.
2217
+ if (params.outputDir !== undefined) {
2218
+ const v = validatePath(params.outputDir, 'outputDir');
2219
+ if (!v.valid)
2220
+ return { success: false, error: v.error };
2221
+ }
2222
+ const outputDir = resolve(getProjectCwd(), params.outputDir || './agents');
2216
2223
  const focus = params.focus || 'all';
2217
2224
  const format = params.format || 'yaml';
2218
2225
  const persist = params.persist !== false; // Default to true
@@ -2255,9 +2262,11 @@ export const hooksBuildAgents = {
2255
2262
  agents: filteredAgents,
2256
2263
  stats: {
2257
2264
  configsGenerated: filteredAgents.length,
2258
- patternsApplied: filteredAgents.length * 3,
2265
+ // #4558: was agents * 3; no pretrain data is read, the configs are templates.
2266
+ patternsApplied: 0,
2259
2267
  optimizationsIncluded: filteredAgents.reduce((acc, a) => acc + a.optimizations.length, 0),
2260
2268
  },
2269
+ _note: 'Configs are built-in templates; pretrain data is not read.',
2261
2270
  };
2262
2271
  },
2263
2272
  };
@@ -612,6 +612,14 @@ export const performanceTools = [
612
612
  };
613
613
  const cpuStats = calcStats(cpuHistory, cpuPercent);
614
614
  const memStats = calcStats(memHistory, memUsedMB);
615
+ // #4554: latency/throughput used to be fixed literals (45ms, 1250 ops/s).
616
+ // Report the samples performance_report records (ADR-093 F8), or say none exist.
617
+ const sampled = (values, unit) => {
618
+ const real = values.filter(n => Number.isFinite(n));
619
+ return real.length === 0
620
+ ? { available: false, unit, samples: 0, _real: false, _note: 'No samples recorded yet: run performance_report to record one.' }
621
+ : { ...calcStats(real, real[real.length - 1]), unit, samples: real.length, _real: true };
622
+ };
615
623
  const allMetrics = {
616
624
  cpu: {
617
625
  ...cpuStats,
@@ -630,30 +638,12 @@ export const performanceTools = [
630
638
  unit: 'MB',
631
639
  _real: true,
632
640
  },
633
- latency: {
634
- current: 45,
635
- avg: 52,
636
- min: 15,
637
- max: 250,
638
- p50: 48,
639
- p95: 150,
640
- p99: 220,
641
- unit: 'ms',
642
- },
643
- throughput: {
644
- current: 1250,
645
- avg: 1100,
646
- min: 500,
647
- max: 2000,
648
- p50: 1050,
649
- p95: 1800,
650
- p99: 1950,
651
- unit: 'ops/s',
652
- },
641
+ latency: sampled(history.map(m => m.latency?.avg), 'ms'),
642
+ throughput: sampled(history.map(m => m.throughput?.operations), 'ops/s'),
653
643
  };
654
644
  if (metric === 'all') {
655
645
  return {
656
- _real: true,
646
+ _real: Object.values(allMetrics).every(m => m._real),
657
647
  metrics: allMetrics,
658
648
  aggregation,
659
649
  historySize: history.length,
@@ -662,9 +652,9 @@ export const performanceTools = [
662
652
  }
663
653
  const selectedMetric = allMetrics[metric];
664
654
  return {
665
- _real: ['cpu', 'memory'].includes(metric),
655
+ _real: selectedMetric._real,
666
656
  metric,
667
- value: selectedMetric[aggregation],
657
+ value: selectedMetric[aggregation] ?? null,
668
658
  unit: selectedMetric.unit,
669
659
  details: selectedMetric,
670
660
  timestamp: new Date().toISOString(),
@@ -54,6 +54,21 @@ function ensureSessionDir() {
54
54
  mkdirRestricted(dir);
55
55
  }
56
56
  }
57
+ /**
58
+ * The session hook (`session.js` from init/helpers-generator.ts) writes its
59
+ * records into this same directory: `current.json` and `session-<n>.json`
60
+ * archives shaped `{ id, startedAt, ... }` rather than session_save's
61
+ * `{ sessionId, name, savedAt, stats }`. Read the id and time from either shape
62
+ * (ADR-093 F6), falling back to the file's own id.
63
+ */
64
+ function sessionIdentity(session, fileId) {
65
+ const raw = session;
66
+ const pick = (...values) => values.find((v) => typeof v === 'string' && v !== '');
67
+ return {
68
+ sessionId: pick(raw.sessionId, raw.id) ?? fileId,
69
+ savedAt: pick(raw.savedAt, raw.startedAt) ?? '',
70
+ };
71
+ }
57
72
  function loadSession(sessionId) {
58
73
  try {
59
74
  const path = getSessionPath(sessionId);
@@ -583,12 +598,13 @@ export const sessionTools = [
583
598
  if (session) {
584
599
  const path = getSessionPath(sessionId);
585
600
  const stat = statSync(path);
601
+ const identity = sessionIdentity(session, sessionId);
586
602
  return {
587
- sessionId: session.sessionId,
588
- name: session.name,
603
+ sessionId: identity.sessionId,
604
+ name: session.name ?? identity.sessionId,
589
605
  description: session.description,
590
- savedAt: session.savedAt,
591
- stats: session.stats,
606
+ savedAt: identity.savedAt,
607
+ stats: session.stats ?? null,
592
608
  fileSize: stat.size,
593
609
  path,
594
610
  hasData: {
@@ -631,12 +647,13 @@ export const sessionTools = [
631
647
  const session = loadSession(sessionId);
632
648
  if (!session)
633
649
  return { sessionId, status: 'unknown', startedAt: '', error: 'Session file unreadable' };
650
+ const identity = sessionIdentity(session, sessionId);
634
651
  return {
635
- sessionId: session.sessionId,
636
- name: session.name,
652
+ sessionId: identity.sessionId,
653
+ name: session.name ?? identity.sessionId,
637
654
  status: 'active',
638
- startedAt: session.savedAt,
639
- stats: session.stats,
655
+ startedAt: identity.savedAt,
656
+ stats: session.stats ?? null,
640
657
  };
641
658
  },
642
659
  },