@jetcrab/pi-desk-usage 1.1.0-dev.1 → 1.1.1-dev

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.
@@ -2,4 +2,5 @@ import type { UsageFileDescriptor } from './usage-scan.js';
2
2
  export declare function pathKey(value: string): string;
3
3
  export declare function nestedParentSessionId(filePath: string): string | null;
4
4
  export declare function descriptor(path: string, nestedSubagent: boolean): Promise<UsageFileDescriptor | null>;
5
+ export declare function listSessionCandidates(root: string, cwd: string, sessionId: string): Promise<UsageFileDescriptor[]>;
5
6
  export declare function listStandardSessionFiles(root: string): Promise<UsageFileDescriptor[]>;
@@ -42,6 +42,30 @@ export async function descriptor(path, nestedSubagent) {
42
42
  function isSessionJsonlFile(name) {
43
43
  return name.endsWith('.jsonl') && !name.includes('.jsonl.');
44
44
  }
45
+ export async function listSessionCandidates(root, cwd, sessionId) {
46
+ const directoryName = `--${resolve(cwd)
47
+ .replace(/^[/\\]/, '')
48
+ .replace(/[/\\:]/g, '-')}--`;
49
+ const suffix = `_${sessionId}.jsonl`.toLowerCase();
50
+ const files = [];
51
+ for (const directory of [join(root, directoryName), root]) {
52
+ let entries;
53
+ try {
54
+ entries = await readdir(directory, { withFileTypes: true });
55
+ }
56
+ catch {
57
+ continue;
58
+ }
59
+ for (const entry of entries) {
60
+ if (!entry.isFile() || !entry.name.toLowerCase().endsWith(suffix))
61
+ continue;
62
+ const file = await descriptor(join(directory, entry.name), false);
63
+ if (file)
64
+ files.push(file);
65
+ }
66
+ }
67
+ return files;
68
+ }
45
69
  async function nestedSessionFiles(directory) {
46
70
  let entries;
47
71
  try {
@@ -6,7 +6,7 @@ import { PluginMethodError } from '@jetcrab/pi-desk-sdk/session';
6
6
  import { usageBucketRanges, usageBucketIndex, zonedHourStart } from './l4-usage-calendar.js';
7
7
  import { z } from 'zod';
8
8
  import { addTotals, aggregateEventModels, emptyStructureCounts, emptyTotals, hasUsage, promptTokens, totalEvents } from './l4-usage-aggregate.js';
9
- import { descriptor, listStandardSessionFiles, nestedParentSessionId, pathKey } from './l4-usage-files.js';
9
+ import { descriptor, listSessionCandidates, listStandardSessionFiles, nestedParentSessionId, pathKey } from './l4-usage-files.js';
10
10
  import { createUsageActivities, createUsageTimeline } from './usage-timeline.js';
11
11
  const HOUR_MS = 60 * 60 * 1000;
12
12
  const HALF_HOUR_MS = HOUR_MS / 2;
@@ -396,10 +396,7 @@ export class UsageRuntime {
396
396
  });
397
397
  }
398
398
  async loadSessionDetails(workSession, signal) {
399
- const main = await this.loadSessionById(workSession.source.sessionId, signal);
400
- if (pathKey(main.file.cwd) !== pathKey(workSession.cwd)) {
401
- throw new PluginMethodError(409, '工作会话目录与 Pi Session 不一致');
402
- }
399
+ const main = await this.loadSessionById(workSession.source.sessionId, workSession.cwd, signal);
403
400
  const parent = main.file.parentSessionPath
404
401
  ? await this.loadExactFile(main.file.parentSessionPath, false, signal)
405
402
  : null;
@@ -517,21 +514,39 @@ export class UsageRuntime {
517
514
  throw new PluginMethodError(409, '工作会话来源已变化');
518
515
  return { source: workSession.source, cwd: workSession.cwd };
519
516
  }
520
- async loadSessionById(sessionId, signal) {
521
- const cached = [...this.cache.values()].find((entry) => !entry.descriptor.nestedSubagent && entry.file.sessionId === sessionId);
517
+ async loadSessionById(sessionId, cwd, signal) {
518
+ const cwdKey = pathKey(cwd);
519
+ const matches = (entry) => entry !== undefined &&
520
+ !entry.descriptor.nestedSubagent &&
521
+ entry.file.sessionId === sessionId &&
522
+ pathKey(entry.file.cwd) === cwdKey;
523
+ const cached = [...this.cache.values()].find(matches);
522
524
  if (cached) {
525
+ const key = pathKey(cached.file.path);
523
526
  const current = await descriptor(cached.file.path, false);
527
+ if (!current || signature(current) !== signature(cached.descriptor)) {
528
+ this.cache.delete(key);
529
+ }
524
530
  if (current)
525
531
  await this.loadChanged([current], signal);
526
- const refreshed = this.cache.get(pathKey(cached.file.path));
527
- if (refreshed)
532
+ const refreshed = this.cache.get(key);
533
+ if (matches(refreshed))
528
534
  return refreshed;
529
535
  }
530
- const standardFiles = await listStandardSessionFiles(this.sessionsRoot);
531
- const suffix = `_${sessionId}.jsonl`.toLowerCase();
532
- const candidates = standardFiles.filter((file) => !file.nestedSubagent && basename(file.path).toLowerCase().endsWith(suffix));
536
+ const candidates = await listSessionCandidates(this.sessionsRoot, cwd, sessionId);
537
+ for (const candidate of candidates) {
538
+ const key = pathKey(candidate.path);
539
+ const previous = this.cache.get(key);
540
+ if (previous && signature(previous.descriptor) !== signature(candidate)) {
541
+ this.cache.delete(key);
542
+ }
543
+ }
533
544
  await this.loadChanged(candidates, signal);
534
- const found = [...this.cache.values()].find((entry) => !entry.descriptor.nestedSubagent && entry.file.sessionId === sessionId);
545
+ const loaded = candidates.map((file) => this.cache.get(pathKey(file.path)));
546
+ const found = loaded.find(matches);
547
+ if (!found && loaded.some((entry) => entry?.file.sessionId === sessionId)) {
548
+ throw new PluginMethodError(409, '工作会话目录与 Pi Session 不一致');
549
+ }
535
550
  if (!found)
536
551
  throw new PluginMethodError(404, 'Pi Session 文件不存在');
537
552
  return found;
package/package.json CHANGED
@@ -5,7 +5,7 @@
5
5
  "url": "git+https://github.com/JetCrab/pi-desk.git",
6
6
  "directory": "plugins/pi-desk-usage"
7
7
  },
8
- "version": "1.1.0-dev.1",
8
+ "version": "1.1.1-dev",
9
9
  "license": "Apache-2.0",
10
10
  "publishConfig": {
11
11
  "registry": "https://registry.npmjs.org",
@@ -42,7 +42,7 @@
42
42
  },
43
43
  "dependencies": {
44
44
  "zod": "^4.4.3",
45
- "@jetcrab/pi-desk-sdk": "^1.1.0-dev.1"
45
+ "@jetcrab/pi-desk-sdk": "^1.1.0"
46
46
  },
47
47
  "peerDependencies": {
48
48
  "@earendil-works/pi-coding-agent": "*"