sidekick-shared 0.22.0 → 0.23.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.
Files changed (120) hide show
  1. package/README.md +75 -63
  2. package/dist/accountManager.js +10 -0
  3. package/dist/accountRegistry.d.ts +2 -0
  4. package/dist/accountStatus.d.ts +1 -0
  5. package/dist/accountStatus.js +6 -0
  6. package/dist/accounts.js +2 -0
  7. package/dist/aggregation/EventAggregator.d.ts +36 -2
  8. package/dist/aggregation/EventAggregator.js +259 -33
  9. package/dist/aggregation/snapshot.d.ts +1 -1
  10. package/dist/aggregation/snapshot.js +2 -1
  11. package/dist/aggregation/types.d.ts +25 -1
  12. package/dist/analytics/codeImpact.d.ts +20 -0
  13. package/dist/analytics/codeImpact.js +21 -0
  14. package/dist/analytics/compactionLedger.d.ts +11 -0
  15. package/dist/analytics/compactionLedger.js +31 -0
  16. package/dist/analytics/qualityScore.d.ts +26 -0
  17. package/dist/analytics/qualityScore.js +115 -0
  18. package/dist/autoSwitch.d.ts +1 -1
  19. package/dist/autoSwitch.js +14 -3
  20. package/dist/codexProfiles.js +2 -0
  21. package/dist/codexQuota.d.ts +2 -0
  22. package/dist/codexQuota.js +27 -0
  23. package/dist/codexQuotaWatcher.d.ts +7 -0
  24. package/dist/codexQuotaWatcher.js +37 -19
  25. package/dist/context/composer.d.ts +3 -2
  26. package/dist/context/composer.js +5 -5
  27. package/dist/context/sessionContext.js +35 -9
  28. package/dist/credentialIO.js +19 -5
  29. package/dist/doctor.d.ts +55 -0
  30. package/dist/doctor.js +244 -0
  31. package/dist/errorHistory.d.ts +33 -0
  32. package/dist/errorHistory.js +95 -0
  33. package/dist/extractors/errorTaxonomy.d.ts +6 -0
  34. package/dist/extractors/errorTaxonomy.js +47 -0
  35. package/dist/extractors/toolCall.d.ts +9 -0
  36. package/dist/extractors/toolCall.js +79 -0
  37. package/dist/handoffUrl.d.ts +7 -0
  38. package/dist/handoffUrl.js +26 -0
  39. package/dist/index.d.ts +32 -9
  40. package/dist/index.js +64 -24
  41. package/dist/modelInfo.js +6 -0
  42. package/dist/multiProviderQuotaService.js +13 -2
  43. package/dist/parsers/codexParser.js +17 -1
  44. package/dist/parsers/openCodeParser.js +13 -0
  45. package/dist/parsers/planExtractor.js +17 -6
  46. package/dist/parsers/sessionActivityDetector.js +2 -0
  47. package/dist/parsers/sessionPathResolver.js +19 -16
  48. package/dist/parsers/subagentScanner.d.ts +1 -1
  49. package/dist/parsers/subagentScanner.js +4 -5
  50. package/dist/parsers/subagentTraceParser.js +4 -7
  51. package/dist/paths.d.ts +10 -0
  52. package/dist/paths.js +18 -5
  53. package/dist/projectMigration.d.ts +23 -0
  54. package/dist/projectMigration.js +95 -0
  55. package/dist/providerStatus.js +13 -2
  56. package/dist/providers/claudeCode.js +18 -4
  57. package/dist/providers/codex.js +42 -13
  58. package/dist/providers/codexDatabase.d.ts +1 -11
  59. package/dist/providers/detect.js +32 -3
  60. package/dist/providers/openCodeDatabase.d.ts +1 -31
  61. package/dist/providers/openCodeDatabase.js +21 -11
  62. package/dist/providers/types.d.ts +1 -0
  63. package/dist/quota.js +8 -0
  64. package/dist/quotaPoller.js +17 -6
  65. package/dist/quotaSnapshots.js +80 -26
  66. package/dist/readers/decisions.d.ts +2 -1
  67. package/dist/readers/decisions.js +5 -5
  68. package/dist/readers/handoff.d.ts +2 -1
  69. package/dist/readers/handoff.js +13 -8
  70. package/dist/readers/helpers.d.ts +3 -0
  71. package/dist/readers/helpers.js +12 -0
  72. package/dist/readers/notes.d.ts +2 -1
  73. package/dist/readers/notes.js +11 -8
  74. package/dist/readers/plans.js +3 -5
  75. package/dist/readers/tasks.d.ts +2 -1
  76. package/dist/readers/tasks.js +5 -5
  77. package/dist/report/htmlHelpers.js +6 -1
  78. package/dist/report/openBrowser.js +3 -3
  79. package/dist/schemas/index.d.ts +2 -0
  80. package/dist/schemas/index.js +10 -1
  81. package/dist/schemas/observedSessionV1.d.ts +499 -0
  82. package/dist/schemas/observedSessionV1.js +69 -0
  83. package/dist/schemas/sessionEvent.d.ts +13 -0
  84. package/dist/schemas/sessionEvent.js +19 -0
  85. package/dist/search/sessionSearch.d.ts +2 -2
  86. package/dist/search/sessionSearch.js +2 -3
  87. package/dist/sessionMonitor.d.ts +29 -0
  88. package/dist/sessionMonitor.js +71 -0
  89. package/dist/statusline/BurnRateCalculator.d.ts +14 -0
  90. package/dist/statusline/BurnRateCalculator.js +45 -0
  91. package/dist/statusline/formatter.d.ts +16 -0
  92. package/dist/statusline/formatter.js +84 -0
  93. package/dist/statusline/index.d.ts +7 -0
  94. package/dist/statusline/index.js +13 -0
  95. package/dist/terminalSync.js +16 -7
  96. package/dist/types/codex.d.ts +2 -0
  97. package/dist/types/historicalData.d.ts +49 -1
  98. package/dist/types/historicalData.js +28 -2
  99. package/dist/types/observedSessionV1.d.ts +70 -0
  100. package/dist/types/observedSessionV1.js +186 -0
  101. package/dist/types/opencode.d.ts +1 -0
  102. package/dist/types/sessionEvent.d.ts +19 -1
  103. package/dist/watchers/eventBridge.js +1 -0
  104. package/dist/watchers/factory.js +14 -3
  105. package/dist/watchers/jsonlTail.js +34 -14
  106. package/dist/watchers/jsonlWatcher.js +5 -3
  107. package/dist/watchers/providerReaderWatcher.js +4 -2
  108. package/dist/watchers/sqliteWatcher.d.ts +2 -0
  109. package/dist/watchers/sqliteWatcher.js +27 -14
  110. package/dist/watchers/types.d.ts +5 -0
  111. package/dist/writers/atomic.d.ts +4 -0
  112. package/dist/writers/atomic.js +200 -0
  113. package/dist/writers/decisions.d.ts +12 -0
  114. package/dist/writers/decisions.js +88 -0
  115. package/dist/writers/notes.d.ts +8 -0
  116. package/dist/writers/notes.js +82 -0
  117. package/dist/writers/tasks.d.ts +6 -0
  118. package/dist/writers/tasks.js +100 -0
  119. package/dist/zaiQuotaWatcher.js +6 -0
  120. package/package.json +5 -1
@@ -64,8 +64,12 @@ class OpenCodeDatabase {
64
64
  };
65
65
  }
66
66
  open() {
67
- if (this.runtimeStatus)
68
- return this.runtimeStatus.available;
67
+ if (this.runtimeStatus?.kind === 'available')
68
+ return true;
69
+ if (this.runtimeStatus &&
70
+ ['db_missing', 'sqlite_missing', 'sqlite_blocked'].includes(this.runtimeStatus.kind)) {
71
+ return false;
72
+ }
69
73
  if (!this.isAvailable()) {
70
74
  this.runtimeStatus = { available: false, kind: 'db_missing' };
71
75
  return false;
@@ -89,15 +93,16 @@ class OpenCodeDatabase {
89
93
  query(sql, params = []) {
90
94
  if (!this.open())
91
95
  return [];
92
- let query = sql;
93
- for (const param of params) {
96
+ let paramIndex = 0;
97
+ const query = sql.replace(/\?/g, () => {
98
+ if (paramIndex >= params.length)
99
+ return '?';
100
+ const param = params[paramIndex++];
94
101
  if (typeof param === 'number')
95
- query = query.replace('?', String(param));
96
- else {
97
- const escaped = String(param).replace(/'/g, "''");
98
- query = query.replace('?', `'${escaped}'`);
99
- }
100
- }
102
+ return Number.isFinite(param) ? String(param) : '0';
103
+ const escaped = String(param).replace(/'/g, "''");
104
+ return `'${escaped}'`;
105
+ });
101
106
  try {
102
107
  const result = (0, child_process_1.execFileSync)('sqlite3', ['-json', '-readonly', this.dbPath, query], {
103
108
  encoding: 'utf-8',
@@ -113,7 +118,12 @@ class OpenCodeDatabase {
113
118
  return JSON.parse(trimmed);
114
119
  }
115
120
  catch (error) {
116
- this.runtimeStatus = toRuntimeStatus(error, 'query_failed');
121
+ this.runtimeStatus = {
122
+ ...toRuntimeStatus(error, 'query_failed'),
123
+ // The database and sqlite binary were successfully opened. A failed
124
+ // query is transient and must not gate the next attempt.
125
+ available: true,
126
+ };
117
127
  return [];
118
128
  }
119
129
  }
@@ -151,6 +151,7 @@ export interface SessionProviderBase {
151
151
  /**
152
152
  * Original simplified session provider interface.
153
153
  * Maintained for backward compatibility with existing CLI and shared code.
154
+ * @deprecated Use SessionProviderBase for new integrations.
154
155
  */
155
156
  export interface SessionProvider {
156
157
  readonly id: ProviderId;
package/dist/quota.js CHANGED
@@ -12,6 +12,7 @@ exports.withQuotaProjections = withQuotaProjections;
12
12
  exports.fetchQuota = fetchQuota;
13
13
  const USAGE_URL = 'https://api.anthropic.com/api/oauth/usage';
14
14
  const BETA_HEADER = 'oauth-2025-04-20';
15
+ const FETCH_TIMEOUT_MS = 10_000;
15
16
  exports.FIVE_HOUR_WINDOW_MS = 5 * 3_600_000;
16
17
  exports.SEVEN_DAY_WINDOW_MS = 7 * 86_400_000;
17
18
  /**
@@ -94,9 +95,13 @@ function parseRetryAfterMs(retryAfter) {
94
95
  * @returns QuotaState with utilization data and elapsed-time projections
95
96
  */
96
97
  async function fetchQuota(accessToken) {
98
+ const controller = new AbortController();
99
+ const timeout = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS);
100
+ timeout.unref();
97
101
  try {
98
102
  const res = await fetch(USAGE_URL, {
99
103
  method: 'GET',
104
+ signal: controller.signal,
100
105
  headers: {
101
106
  Authorization: `Bearer ${accessToken}`,
102
107
  'anthropic-beta': BETA_HEADER,
@@ -152,4 +157,7 @@ async function fetchQuota(accessToken) {
152
157
  catch {
153
158
  return unavailableState('Network error', { failureKind: 'network' });
154
159
  }
160
+ finally {
161
+ clearTimeout(timeout);
162
+ }
155
163
  }
@@ -111,10 +111,12 @@ class QuotaPoller {
111
111
  this.notify(state);
112
112
  }
113
113
  else if (state.failureKind === 'auth') {
114
- // Auth errors: stop polling, notify with error state
114
+ // Keep polling at the idle cadence so signing in again recovers
115
+ // without an extension/CLI restart.
115
116
  this.latest = state;
116
117
  this.notify(state);
117
- this.stop();
118
+ this.consecutiveFailures = 0;
119
+ this.scheduleNext(this.idleIntervalMs);
118
120
  return;
119
121
  }
120
122
  else {
@@ -134,17 +136,26 @@ class QuotaPoller {
134
136
  }
135
137
  }
136
138
  }
137
- catch {
139
+ catch (error) {
138
140
  this.consecutiveFailures++;
141
+ const state = {
142
+ fiveHour: { utilization: 0, resetsAt: '' },
143
+ sevenDay: { utilization: 0, resetsAt: '' },
144
+ available: false,
145
+ error: error instanceof Error ? error.message : String(error),
146
+ failureKind: 'auth',
147
+ };
148
+ this.latest = state;
149
+ this.notify(state);
139
150
  }
140
151
  this.scheduleNext();
141
152
  }
142
- scheduleNext() {
153
+ scheduleNext(overrideMs) {
143
154
  if (this.stopped)
144
155
  return;
145
156
  const baseInterval = this.isActive ? this.activeIntervalMs : this.idleIntervalMs;
146
- const backoff = Math.min(baseInterval * Math.pow(2, this.consecutiveFailures), this.maxBackoffMs);
147
- const delay = this.consecutiveFailures > 0 ? backoff : baseInterval;
157
+ const backoff = Math.min(baseInterval * Math.pow(2, this.consecutiveFailures), Math.max(this.maxBackoffMs, baseInterval));
158
+ const delay = overrideMs ?? (this.consecutiveFailures > 0 ? backoff : baseInterval);
148
159
  this.timer = setTimeout(() => void this.poll(), delay);
149
160
  }
150
161
  notify(state) {
@@ -40,6 +40,10 @@ const fs = __importStar(require("fs"));
40
40
  const path = __importStar(require("path"));
41
41
  const paths_1 = require("./paths");
42
42
  const codex_1 = require("./types/codex");
43
+ const LOCK_WAIT_MS = 15;
44
+ const LOCK_STALE_MS = 10_000;
45
+ const LOCK_TIMEOUT_MS = 15_000;
46
+ const lockSleepView = new Int32Array(new SharedArrayBuffer(4));
43
47
  function getQuotaSnapshotPath() {
44
48
  return path.join((0, paths_1.getConfigDir)(), 'quota-snapshots.json');
45
49
  }
@@ -84,6 +88,61 @@ function writeStore(store) {
84
88
  ensureConfigDir();
85
89
  atomicWriteJson(getQuotaSnapshotPath(), store);
86
90
  }
91
+ function withSnapshotStoreLock(operation) {
92
+ ensureConfigDir();
93
+ const lockPath = `${getQuotaSnapshotPath()}.lock`;
94
+ const token = `${process.pid}:${crypto.randomBytes(16).toString('hex')}`;
95
+ const deadline = Date.now() + LOCK_TIMEOUT_MS;
96
+ while (true) {
97
+ try {
98
+ const descriptor = fs.openSync(lockPath, 'wx', 0o600);
99
+ fs.writeFileSync(descriptor, token, 'utf8');
100
+ fs.fsyncSync(descriptor);
101
+ fs.closeSync(descriptor);
102
+ break;
103
+ }
104
+ catch (error) {
105
+ if (error.code !== 'EEXIST')
106
+ throw error;
107
+ try {
108
+ const stat = fs.statSync(lockPath);
109
+ if (Date.now() - stat.mtimeMs > LOCK_STALE_MS) {
110
+ const owner = fs.readFileSync(lockPath, 'utf8');
111
+ const ownerPid = Number.parseInt(owner.split(':', 1)[0], 10);
112
+ let ownerAlive = Number.isInteger(ownerPid) && ownerPid > 0;
113
+ if (ownerAlive) {
114
+ try {
115
+ process.kill(ownerPid, 0);
116
+ }
117
+ catch (ownerError) {
118
+ ownerAlive = ownerError.code === 'EPERM';
119
+ }
120
+ }
121
+ if (!ownerAlive)
122
+ fs.rmSync(lockPath, { force: true });
123
+ }
124
+ }
125
+ catch {
126
+ // The owner may have released the lock between checks.
127
+ }
128
+ if (Date.now() >= deadline)
129
+ throw new Error(`Timed out waiting for quota lock: ${lockPath}`);
130
+ Atomics.wait(lockSleepView, 0, 0, LOCK_WAIT_MS);
131
+ }
132
+ }
133
+ try {
134
+ return operation();
135
+ }
136
+ finally {
137
+ try {
138
+ if (fs.readFileSync(lockPath, 'utf8') === token)
139
+ fs.rmSync(lockPath, { force: true });
140
+ }
141
+ catch {
142
+ // Never remove a lock that has already been released or replaced.
143
+ }
144
+ }
145
+ }
87
146
  function snapshotTimeMs(quota) {
88
147
  const capturedAt = quota.capturedAt ? Date.parse(quota.capturedAt) : NaN;
89
148
  return Number.isFinite(capturedAt) ? capturedAt : 0;
@@ -119,32 +178,27 @@ function shouldKeepExistingSnapshot(existing, next) {
119
178
  return snapshotTimeMs(existing) > snapshotTimeMs(next);
120
179
  }
121
180
  function writeQuotaSnapshot(providerId, accountId, quota) {
122
- const store = readStore();
123
- const index = store.snapshots.findIndex((item) => item.providerId === providerId && item.accountId === accountId);
124
- const existingQuota = index >= 0 ? store.snapshots[index].quota : undefined;
125
- const snapshot = {
126
- ...quota,
127
- providerId,
128
- capturedAt: quota.capturedAt ?? new Date().toISOString(),
129
- source: quota.source ?? 'session',
130
- stale: false,
131
- resetCredits: quota.resetCredits ?? (providerId === 'codex' ? existingQuota?.resetCredits : undefined),
132
- };
133
- if (index >= 0 && shouldKeepExistingSnapshot(store.snapshots[index].quota, snapshot)) {
134
- return;
135
- }
136
- const record = {
137
- providerId,
138
- accountId,
139
- quota: snapshot,
140
- };
141
- if (index >= 0) {
142
- store.snapshots[index] = record;
143
- }
144
- else {
145
- store.snapshots.push(record);
146
- }
147
- writeStore(store);
181
+ withSnapshotStoreLock(() => {
182
+ const store = readStore();
183
+ const index = store.snapshots.findIndex((item) => item.providerId === providerId && item.accountId === accountId);
184
+ const existingQuota = index >= 0 ? store.snapshots[index].quota : undefined;
185
+ const snapshot = {
186
+ ...quota,
187
+ providerId,
188
+ capturedAt: quota.capturedAt ?? new Date().toISOString(),
189
+ source: quota.source ?? 'session',
190
+ stale: false,
191
+ resetCredits: quota.resetCredits ?? (providerId === 'codex' ? existingQuota?.resetCredits : undefined),
192
+ };
193
+ if (index >= 0 && shouldKeepExistingSnapshot(store.snapshots[index].quota, snapshot))
194
+ return;
195
+ const record = { providerId, accountId, quota: snapshot };
196
+ if (index >= 0)
197
+ store.snapshots[index] = record;
198
+ else
199
+ store.snapshots.push(record);
200
+ writeStore(store);
201
+ });
148
202
  }
149
203
  function readQuotaSnapshot(providerId, accountId) {
150
204
  const store = readStore();
@@ -2,9 +2,10 @@
2
2
  * Reader for persisted decisions.
3
3
  */
4
4
  import type { DecisionEntry } from '../types/decisionLog';
5
+ import type { ProjectIdentity } from '../paths';
5
6
  export interface ReadDecisionsOptions {
6
7
  search?: string;
7
8
  file?: string;
8
9
  limit?: number;
9
10
  }
10
- export declare function readDecisions(slug: string, opts?: ReadDecisionsOptions): Promise<DecisionEntry[]>;
11
+ export declare function readDecisions(project: string | ProjectIdentity, opts?: ReadDecisionsOptions): Promise<DecisionEntry[]>;
@@ -4,14 +4,14 @@
4
4
  */
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.readDecisions = readDecisions;
7
- const paths_1 = require("../paths");
8
7
  const helpers_1 = require("./helpers");
9
- async function readDecisions(slug, opts) {
10
- const filePath = (0, paths_1.getProjectDataPath)(slug, 'decisions');
11
- const store = await (0, helpers_1.readJsonStore)(filePath);
8
+ async function readDecisions(project, opts) {
9
+ const store = await (0, helpers_1.readProjectJsonStore)(project, 'decisions');
12
10
  if (!store)
13
11
  return [];
14
- let decisions = Object.values(store.decisions);
12
+ let decisions = store.decisions && typeof store.decisions === 'object' && !Array.isArray(store.decisions)
13
+ ? Object.values(store.decisions)
14
+ : [];
15
15
  // Sort by timestamp descending
16
16
  decisions.sort((a, b) => new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime());
17
17
  if (opts?.search) {
@@ -1,4 +1,5 @@
1
1
  /**
2
2
  * Reader for session handoff documents.
3
3
  */
4
- export declare function readLatestHandoff(slug: string): Promise<string | null>;
4
+ import { type ProjectIdentity } from '../paths';
5
+ export declare function readLatestHandoff(project: string | ProjectIdentity): Promise<string | null>;
@@ -39,13 +39,18 @@ Object.defineProperty(exports, "__esModule", { value: true });
39
39
  exports.readLatestHandoff = readLatestHandoff;
40
40
  const fs = __importStar(require("fs"));
41
41
  const paths_1 = require("../paths");
42
- async function readLatestHandoff(slug) {
43
- const filePath = (0, paths_1.getProjectDataPath)(slug, 'handoffs').replace('.json', '-latest.md');
44
- try {
45
- const content = await fs.promises.readFile(filePath, 'utf-8');
46
- return content.trim() || null;
47
- }
48
- catch {
49
- return null;
42
+ async function readLatestHandoff(project) {
43
+ const candidates = typeof project === 'string' ? [project] : project.candidates;
44
+ for (const slug of candidates) {
45
+ const filePath = (0, paths_1.getProjectDataPath)(slug, 'handoffs').replace(/\.json$/, '-latest.md');
46
+ try {
47
+ const content = await fs.promises.readFile(filePath, 'utf-8');
48
+ if (content.trim())
49
+ return content.trim();
50
+ }
51
+ catch {
52
+ // Try the legacy candidate.
53
+ }
50
54
  }
55
+ return null;
51
56
  }
@@ -1,7 +1,10 @@
1
1
  /**
2
2
  * Shared reader helpers.
3
3
  */
4
+ import { type ProjectIdentity } from '../paths';
4
5
  /**
5
6
  * Reads and parses a JSON store file. Returns null if file missing or malformed.
6
7
  */
7
8
  export declare function readJsonStore<T>(filePath: string): Promise<T | null>;
9
+ /** Reads the canonical project store, falling back to the legacy symlink slug. */
10
+ export declare function readProjectJsonStore<T>(project: string | ProjectIdentity, subdomain: string): Promise<T | null>;
@@ -37,7 +37,9 @@ var __importStar = (this && this.__importStar) || (function () {
37
37
  })();
38
38
  Object.defineProperty(exports, "__esModule", { value: true });
39
39
  exports.readJsonStore = readJsonStore;
40
+ exports.readProjectJsonStore = readProjectJsonStore;
40
41
  const fs = __importStar(require("fs"));
42
+ const paths_1 = require("../paths");
41
43
  /**
42
44
  * Reads and parses a JSON store file. Returns null if file missing or malformed.
43
45
  */
@@ -50,3 +52,13 @@ async function readJsonStore(filePath) {
50
52
  return null;
51
53
  }
52
54
  }
55
+ /** Reads the canonical project store, falling back to the legacy symlink slug. */
56
+ async function readProjectJsonStore(project, subdomain) {
57
+ const candidates = typeof project === 'string' ? [project] : project.candidates;
58
+ for (const slug of candidates) {
59
+ const store = await readJsonStore((0, paths_1.getProjectDataPath)(slug, subdomain));
60
+ if (store)
61
+ return store;
62
+ }
63
+ return null;
64
+ }
@@ -2,9 +2,10 @@
2
2
  * Reader for knowledge notes.
3
3
  */
4
4
  import type { KnowledgeNote, KnowledgeNoteType, KnowledgeNoteStatus } from '../types/knowledgeNote';
5
+ import type { ProjectIdentity } from '../paths';
5
6
  export interface ReadNotesOptions {
6
7
  file?: string;
7
8
  type?: KnowledgeNoteType;
8
9
  status?: KnowledgeNoteStatus;
9
10
  }
10
- export declare function readNotes(slug: string, opts?: ReadNotesOptions): Promise<KnowledgeNote[]>;
11
+ export declare function readNotes(project: string | ProjectIdentity, opts?: ReadNotesOptions): Promise<KnowledgeNote[]>;
@@ -4,29 +4,32 @@
4
4
  */
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.readNotes = readNotes;
7
- const paths_1 = require("../paths");
8
7
  const helpers_1 = require("./helpers");
9
- async function readNotes(slug, opts) {
10
- const filePath = (0, paths_1.getProjectDataPath)(slug, 'knowledge-notes');
11
- const store = await (0, helpers_1.readJsonStore)(filePath);
8
+ async function readNotes(project, opts) {
9
+ const store = await (0, helpers_1.readProjectJsonStore)(project, 'knowledge-notes');
12
10
  if (!store)
13
11
  return [];
14
12
  let notes = [];
13
+ const notesByFile = store.notesByFile && typeof store.notesByFile === 'object' && !Array.isArray(store.notesByFile)
14
+ ? store.notesByFile
15
+ : {};
15
16
  if (opts?.file) {
16
17
  // Normalize file path for comparison
17
18
  const normalizedFile = opts.file.replace(/\\/g, '/');
18
- for (const [filePath, fileNotes] of Object.entries(store.notesByFile)) {
19
+ for (const [filePath, fileNotes] of Object.entries(notesByFile)) {
19
20
  const normalizedKey = filePath.replace(/\\/g, '/');
20
21
  if (normalizedKey === normalizedFile ||
21
22
  normalizedKey.endsWith('/' + normalizedFile) ||
22
23
  normalizedFile.endsWith('/' + normalizedKey)) {
23
- notes.push(...fileNotes);
24
+ if (Array.isArray(fileNotes))
25
+ notes.push(...fileNotes);
24
26
  }
25
27
  }
26
28
  }
27
29
  else {
28
- for (const fileNotes of Object.values(store.notesByFile)) {
29
- notes.push(...fileNotes);
30
+ for (const fileNotes of Object.values(notesByFile)) {
31
+ if (Array.isArray(fileNotes))
32
+ notes.push(...fileNotes);
30
33
  }
31
34
  }
32
35
  if (opts?.type) {
@@ -48,12 +48,13 @@ const plan_1 = require("../types/plan");
48
48
  const paths_1 = require("../paths");
49
49
  const helpers_1 = require("./helpers");
50
50
  const planExtractor_1 = require("../parsers/planExtractor");
51
+ const atomic_1 = require("../writers/atomic");
51
52
  async function readPlans(slug, opts) {
52
53
  const filePath = (0, paths_1.getProjectDataPath)(slug, 'plans');
53
54
  const store = await (0, helpers_1.readJsonStore)(filePath);
54
55
  if (!store)
55
56
  return [];
56
- let plans = [...store.plans];
57
+ let plans = Array.isArray(store.plans) ? [...store.plans] : [];
57
58
  const status = opts?.status ?? 'all';
58
59
  if (status !== 'all') {
59
60
  plans = plans.filter((p) => p.status === status);
@@ -170,10 +171,7 @@ async function writePlans(slug, plans) {
170
171
  plans: capped,
171
172
  lastSaved: new Date().toISOString(),
172
173
  };
173
- // Ensure directory exists
174
- const dir = filePath.replace(/[/\\][^/\\]+$/, '');
175
- await fs.promises.mkdir(dir, { recursive: true });
176
- await fs.promises.writeFile(filePath, JSON.stringify(store, null, 2), 'utf-8');
174
+ await (0, atomic_1.atomicWriteJson)(filePath, store);
177
175
  }
178
176
  /**
179
177
  * Reads raw plan markdown files from ~/.claude/plans/ that belong to
@@ -2,7 +2,8 @@
2
2
  * Reader for persisted tasks.
3
3
  */
4
4
  import type { PersistedTask } from '../types/taskPersistence';
5
+ import type { ProjectIdentity } from '../paths';
5
6
  export interface ReadTasksOptions {
6
7
  status?: 'pending' | 'completed' | 'all';
7
8
  }
8
- export declare function readTasks(slug: string, opts?: ReadTasksOptions): Promise<PersistedTask[]>;
9
+ export declare function readTasks(project: string | ProjectIdentity, opts?: ReadTasksOptions): Promise<PersistedTask[]>;
@@ -4,14 +4,14 @@
4
4
  */
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.readTasks = readTasks;
7
- const paths_1 = require("../paths");
8
7
  const helpers_1 = require("./helpers");
9
- async function readTasks(slug, opts) {
10
- const filePath = (0, paths_1.getProjectDataPath)(slug, 'tasks');
11
- const store = await (0, helpers_1.readJsonStore)(filePath);
8
+ async function readTasks(project, opts) {
9
+ const store = await (0, helpers_1.readProjectJsonStore)(project, 'tasks');
12
10
  if (!store)
13
11
  return [];
14
- let tasks = Object.values(store.tasks);
12
+ let tasks = store.tasks && typeof store.tasks === 'object' && !Array.isArray(store.tasks)
13
+ ? Object.values(store.tasks)
14
+ : [];
15
15
  const status = opts?.status ?? 'all';
16
16
  if (status !== 'all') {
17
17
  tasks = tasks.filter((t) => t.status === status);
@@ -53,7 +53,12 @@ function simpleMarkdownToHtml(text) {
53
53
  // Inline code (after escaping so backtick content is already safe)
54
54
  result = result.replace(/`([^`]+)`/g, '<code class="inline-code">$1</code>');
55
55
  // Links [text](url)
56
- result = result.replace(/\[([^\]]+)\]\(([^)]+)\)/g, '<a href="$2" target="_blank" rel="noopener">$1</a>');
56
+ result = result.replace(/\[([^\]]+)\]\(([^)]+)\)/g, (match, label, url) => {
57
+ const trimmedUrl = url.trim();
58
+ if (!/^(?:https?:|mailto:|#|\/)/i.test(trimmedUrl))
59
+ return match;
60
+ return `<a href="${trimmedUrl}" target="_blank" rel="noopener">${label}</a>`;
61
+ });
57
62
  // Unordered lists
58
63
  result = result.replace(/^(\s*)[-*] (.+)$/gm, '$1<li>$2</li>');
59
64
  // Ordered lists
@@ -10,13 +10,13 @@ function openInBrowser(filePath) {
10
10
  const url = `file://${filePath}`;
11
11
  switch (process.platform) {
12
12
  case 'darwin':
13
- (0, child_process_1.exec)(`open "${url}"`);
13
+ (0, child_process_1.execFile)('open', [url]);
14
14
  break;
15
15
  case 'win32':
16
- (0, child_process_1.exec)(`start "" "${url}"`);
16
+ (0, child_process_1.execFile)('rundll32.exe', ['url.dll,FileProtocolHandler', url]);
17
17
  break;
18
18
  default:
19
- (0, child_process_1.exec)(`xdg-open "${url}"`);
19
+ (0, child_process_1.execFile)('xdg-open', [url]);
20
20
  break;
21
21
  }
22
22
  }
@@ -12,6 +12,8 @@
12
12
  */
13
13
  export { messageUsageSchema, sessionMessageSchema, sessionEventSchema, permissionModeSchema, extractSessionEvents, } from './sessionEvent';
14
14
  export type { MessageUsage, SessionMessage, SessionEvent, PermissionMode, } from '../types/sessionEvent';
15
+ export { observationProvenanceV1Schema, observedAgentSessionV1Schema, observedValueV1Schema, pendingUserRequestV1Schema, providerCapabilitiesV1Schema, providerSessionAdapterV1Schema, sessionEvidenceRefV1Schema, } from './observedSessionV1';
16
+ export type { ObservationProvenanceV1, ObservedAgentSessionV1, ObservedValueV1, PendingUserRequestV1, ProviderCapabilitiesV1, ProviderSessionAdapterV1, SessionEvidenceRefV1, } from '../types/observedSessionV1';
15
17
  export { quotaWindowSchema, quotaStateSchema, quotaFailureKindSchema, quotaProviderIdSchema, quotaSourceSchema, codexResetCreditSchema, codexResetCreditsSnapshotSchema, peakHoursStateSchema, quotaFailureDescriptorSchema, runtimeQuotaProviderSchema, providerQuotaStateSchema, claudeProviderQuotaStateSchema, codexProviderQuotaStateSchema, zaiProviderQuotaStateSchema, providerQuotaMapSchema, } from './quota';
16
18
  export type { CodexResetCredit, CodexResetCreditsSnapshot, QuotaWindow, QuotaState, } from '../quota';
17
19
  export type { PeakHoursState } from '../peakHours';
@@ -12,13 +12,22 @@
12
12
  * subpath is self-sufficient for boundary-validation modules.
13
13
  */
14
14
  Object.defineProperty(exports, "__esModule", { value: true });
15
- exports.assistantTurnToolRefSchema = exports.assistantTurnToolGroupStepSchema = exports.assistantTurnTimelineItemSchema = exports.assistantTurnSubagentStatusSchema = exports.assistantTurnSubagentSchema = exports.assistantTurnReasoningTimelineItemSchema = exports.assistantTurnProjectionSchema = exports.assistantTurnProcessStepSchema = exports.assistantTurnProcessSchema = exports.assistantTurnNarrationStepSchema = exports.assistantTurnEventTypeSchema = exports.assistantTurnEventSchema = exports.listAllAccountsResultSchema = exports.savedAccountProfileSchema = exports.accountEntrySchema = exports.accountLoginStatusSchema = exports.beginAccountLoginResultSchema = exports.accountManagerResultSchema = exports.accountProviderIdSchema = exports.activeAccountStatusSchema = exports.activeProviderAccountStatusSchema = exports.quotaHistoryDailyBucketSchema = exports.quotaHistorySampleSchema = exports.quotaHistoryRuntimeProviderSchema = exports.providerQuotaMapSchema = exports.zaiProviderQuotaStateSchema = exports.codexProviderQuotaStateSchema = exports.claudeProviderQuotaStateSchema = exports.providerQuotaStateSchema = exports.runtimeQuotaProviderSchema = exports.quotaFailureDescriptorSchema = exports.peakHoursStateSchema = exports.codexResetCreditsSnapshotSchema = exports.codexResetCreditSchema = exports.quotaSourceSchema = exports.quotaProviderIdSchema = exports.quotaFailureKindSchema = exports.quotaStateSchema = exports.quotaWindowSchema = exports.extractSessionEvents = exports.permissionModeSchema = exports.sessionEventSchema = exports.sessionMessageSchema = exports.messageUsageSchema = void 0;
15
+ exports.assistantTurnToolGroupStepSchema = exports.assistantTurnTimelineItemSchema = exports.assistantTurnSubagentStatusSchema = exports.assistantTurnSubagentSchema = exports.assistantTurnReasoningTimelineItemSchema = exports.assistantTurnProjectionSchema = exports.assistantTurnProcessStepSchema = exports.assistantTurnProcessSchema = exports.assistantTurnNarrationStepSchema = exports.assistantTurnEventTypeSchema = exports.assistantTurnEventSchema = exports.listAllAccountsResultSchema = exports.savedAccountProfileSchema = exports.accountEntrySchema = exports.accountLoginStatusSchema = exports.beginAccountLoginResultSchema = exports.accountManagerResultSchema = exports.accountProviderIdSchema = exports.activeAccountStatusSchema = exports.activeProviderAccountStatusSchema = exports.quotaHistoryDailyBucketSchema = exports.quotaHistorySampleSchema = exports.quotaHistoryRuntimeProviderSchema = exports.providerQuotaMapSchema = exports.zaiProviderQuotaStateSchema = exports.codexProviderQuotaStateSchema = exports.claudeProviderQuotaStateSchema = exports.providerQuotaStateSchema = exports.runtimeQuotaProviderSchema = exports.quotaFailureDescriptorSchema = exports.peakHoursStateSchema = exports.codexResetCreditsSnapshotSchema = exports.codexResetCreditSchema = exports.quotaSourceSchema = exports.quotaProviderIdSchema = exports.quotaFailureKindSchema = exports.quotaStateSchema = exports.quotaWindowSchema = exports.sessionEvidenceRefV1Schema = exports.providerSessionAdapterV1Schema = exports.providerCapabilitiesV1Schema = exports.pendingUserRequestV1Schema = exports.observedValueV1Schema = exports.observedAgentSessionV1Schema = exports.observationProvenanceV1Schema = exports.extractSessionEvents = exports.permissionModeSchema = exports.sessionEventSchema = exports.sessionMessageSchema = exports.messageUsageSchema = void 0;
16
+ exports.assistantTurnToolRefSchema = void 0;
16
17
  var sessionEvent_1 = require("./sessionEvent");
17
18
  Object.defineProperty(exports, "messageUsageSchema", { enumerable: true, get: function () { return sessionEvent_1.messageUsageSchema; } });
18
19
  Object.defineProperty(exports, "sessionMessageSchema", { enumerable: true, get: function () { return sessionEvent_1.sessionMessageSchema; } });
19
20
  Object.defineProperty(exports, "sessionEventSchema", { enumerable: true, get: function () { return sessionEvent_1.sessionEventSchema; } });
20
21
  Object.defineProperty(exports, "permissionModeSchema", { enumerable: true, get: function () { return sessionEvent_1.permissionModeSchema; } });
21
22
  Object.defineProperty(exports, "extractSessionEvents", { enumerable: true, get: function () { return sessionEvent_1.extractSessionEvents; } });
23
+ var observedSessionV1_1 = require("./observedSessionV1");
24
+ Object.defineProperty(exports, "observationProvenanceV1Schema", { enumerable: true, get: function () { return observedSessionV1_1.observationProvenanceV1Schema; } });
25
+ Object.defineProperty(exports, "observedAgentSessionV1Schema", { enumerable: true, get: function () { return observedSessionV1_1.observedAgentSessionV1Schema; } });
26
+ Object.defineProperty(exports, "observedValueV1Schema", { enumerable: true, get: function () { return observedSessionV1_1.observedValueV1Schema; } });
27
+ Object.defineProperty(exports, "pendingUserRequestV1Schema", { enumerable: true, get: function () { return observedSessionV1_1.pendingUserRequestV1Schema; } });
28
+ Object.defineProperty(exports, "providerCapabilitiesV1Schema", { enumerable: true, get: function () { return observedSessionV1_1.providerCapabilitiesV1Schema; } });
29
+ Object.defineProperty(exports, "providerSessionAdapterV1Schema", { enumerable: true, get: function () { return observedSessionV1_1.providerSessionAdapterV1Schema; } });
30
+ Object.defineProperty(exports, "sessionEvidenceRefV1Schema", { enumerable: true, get: function () { return observedSessionV1_1.sessionEvidenceRefV1Schema; } });
22
31
  var quota_1 = require("./quota");
23
32
  Object.defineProperty(exports, "quotaWindowSchema", { enumerable: true, get: function () { return quota_1.quotaWindowSchema; } });
24
33
  Object.defineProperty(exports, "quotaStateSchema", { enumerable: true, get: function () { return quota_1.quotaStateSchema; } });