claude-code-session-manager 0.112.0 → 0.114.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/.claude-plugin/marketplace.json +1 -1
- package/dist/assets/{AgentLibrary-RwPvEgqm.js → AgentLibrary-DZFM7Lto.js} +2 -2
- package/dist/assets/{Button-36j3PzYv.js → Button-BJVFKdGH.js} +1 -1
- package/dist/assets/{DataModel-DUhANo4l.js → DataModel-CebvcP_y.js} +1 -1
- package/dist/assets/{History-BeZzNjmv.js → History-BwISEUhR.js} +2 -2
- package/dist/assets/{Hooks-iQ8845Hp.js → Hooks-Dac3smYP.js} +3 -3
- package/dist/assets/{Library-DPE1h-1L.js → Library-DTjsUr9W.js} +7 -7
- package/dist/assets/{ListDetail-PStOuQAo.js → ListDetail-B1L5TbjS.js} +1 -1
- package/dist/assets/{MarkdownEditor-DPw-N2-m.js → MarkdownEditor-BFRhWZD4.js} +1 -1
- package/dist/assets/{McpServers-CUyRTNep.js → McpServers-CBjGCMg0.js} +2 -2
- package/dist/assets/Memory-DdLi5fqe.js +8 -0
- package/dist/assets/{Panel-_VZ_uXNg.js → Panel-BQkkkjex.js} +1 -1
- package/dist/assets/{Permissions-PPRmfNbK.js → Permissions-CaACwY0B.js} +3 -3
- package/dist/assets/{Plugins-BFcvfvlx.js → Plugins-DJeBwVoh.js} +2 -2
- package/dist/assets/ProvenanceBadge--N5j8xiK.js +1 -0
- package/dist/assets/{RemoteMachines-D5V0lzgW.js → RemoteMachines-WjWaDHEA.js} +2 -2
- package/dist/assets/{SaveBar-Dw64FVdM.js → SaveBar-DFmOjb4_.js} +1 -1
- package/dist/assets/{Scheduler-C8s_pJ3I.js → Scheduler-Miap6_aY.js} +8 -8
- package/dist/assets/{ScopeSwitcher-D-nKDMAE.js → ScopeSwitcher-CiEujp4L.js} +1 -1
- package/dist/assets/Settings-UGpW4P0j.js +4 -0
- package/dist/assets/{SkillReferenceGraph-BbZFMHim.js → SkillReferenceGraph-BgyAnxGW.js} +1 -1
- package/dist/assets/Skills-BQUpnNfx.js +2 -0
- package/dist/assets/SystemPrompt-CQjOgHGT.js +1 -0
- package/dist/assets/TagLibrary-CTVpoLl0.js +1 -0
- package/dist/assets/{TiptapBody-Cr-hxX8L.js → TiptapBody-BtOcrdRR.js} +1 -1
- package/dist/assets/{Toggle-7WpPzcZH.js → Toggle-Chri4G19.js} +1 -1
- package/dist/assets/{ViewTabs-9lQCRwWJ.js → ViewTabs-BIenDGVv.js} +1 -1
- package/dist/assets/{historyTotals-BrFxw5J1.js → historyTotals-CX8mUxJY.js} +1 -1
- package/dist/assets/{index-CTBpf-26.js → index-AVrhGmUO.js} +163 -163
- package/dist/assets/{index-Dutcsly-.css → index-HpEbvDiE.css} +1 -1
- package/dist/assets/{settingsSchema-Dh5Kl7VO.js → settingsSchema-uqwlznbH.js} +1 -1
- package/dist/assets/{useAgentsDirWatch-CMpx0d04.js → useAgentsDirWatch-BaezRtWw.js} +1 -1
- package/dist/assets/{useRemotePeers-CI2axd-L.js → useRemotePeers-BuFbBqDt.js} +1 -1
- package/dist/index.html +2 -2
- package/package.json +1 -1
- package/plugins/session-manager-dev/.claude-plugin/plugin.json +1 -1
- package/plugins/session-manager-dev/skills/send-feedback/SKILL.md +13 -0
- package/scripts/scheduler-mcp-server.cjs +23 -5
- package/src/main/build-info.json +4 -4
- package/src/main/chatRunner.cjs +18 -1
- package/src/main/config.cjs +18 -1
- package/src/main/index.cjs +6 -2
- package/src/main/ipcSchemas.cjs +27 -34
- package/src/main/lib/activeSessions.cjs +13 -0
- package/src/main/lib/claude/rateLimitDetect.cjs +29 -10
- package/src/main/lib/claude/rateLimitWindow.cjs +23 -11
- package/src/main/lib/credentials.cjs +16 -90
- package/src/main/lib/feedback-remote/crossProjectFeedback.cjs +107 -9
- package/src/main/lib/feedback-remote/humanPromptLedger.cjs +93 -0
- package/src/main/lib/mcpToolCatalog.cjs +9 -3
- package/src/main/lib/memoryDirResolve.cjs +139 -0
- package/src/main/lib/memoryIndex.cjs +98 -0
- package/src/main/lib/prd/depSlugResolve.cjs +16 -1
- package/src/main/lib/scheduler/finalizeReason.cjs +30 -0
- package/src/main/lib/scheduler/flatSweepThrottle.cjs +17 -0
- package/src/main/lib/scheduler/runLogRetention.cjs +34 -13
- package/src/main/lib/scheduler/schedulerBatch.cjs +8 -12
- package/src/main/lib/telemetry/telemetryClient.cjs +8 -4
- package/src/main/lib/util/atomicFs.cjs +42 -0
- package/src/main/lib/util/loopDelay.cjs +1 -1
- package/src/main/memoryAggregate.cjs +15 -8
- package/src/main/memoryTool.cjs +151 -22
- package/src/main/runVerify.cjs +5 -2
- package/src/main/scheduler.cjs +43 -10
- package/src/main/supervisor.cjs +45 -22
- package/src/main/webRemote.cjs +15 -0
- package/src/preload/api.d.ts +47 -52
- package/src/preload/index.cjs +25 -21
- package/dist/assets/Memory-bZ2xjBGy.js +0 -8
- package/dist/assets/ProvenanceBadge-B1WUknak.js +0 -1
- package/dist/assets/Settings-CQSrR4mO.js +0 -4
- package/dist/assets/Skills-Bzyrh6EQ.js +0 -2
- package/dist/assets/SystemPrompt-DP6i3Dx0.js +0 -1
- package/dist/assets/TagLibrary-iip0C6Bl.js +0 -1
- package/src/main/agentMemory.cjs +0 -246
package/src/main/scheduler.cjs
CHANGED
|
@@ -65,6 +65,8 @@ const { headlessPermissionArgs, ensureCliCapsProbed } = require('./lib/claude/cl
|
|
|
65
65
|
// as the authoritative guarantee.
|
|
66
66
|
ensureCliCapsProbed().catch(() => {});
|
|
67
67
|
const launchFailure = require('./lib/scheduler/launchFailure.cjs');
|
|
68
|
+
const { finalizeReasonFor } = require('./lib/scheduler/finalizeReason.cjs');
|
|
69
|
+
const { shouldRunFlatSweep, FLAT_SWEEP_INTERVAL_MS } = require('./lib/scheduler/flatSweepThrottle.cjs');
|
|
68
70
|
const { appendError } = require('./lib/telemetry/opsErrorLog.cjs');
|
|
69
71
|
const { readTail } = require('./lib/util/fileTail.cjs');
|
|
70
72
|
const {
|
|
@@ -1389,6 +1391,9 @@ function reconcileSourcePromptId(job, parsedSourcePromptId) {
|
|
|
1389
1391
|
// process rather than one per 60s reconcile tick.
|
|
1390
1392
|
const warnedUnregisteredEpics = new Set();
|
|
1391
1393
|
|
|
1394
|
+
/** @type {number | null} last flat-sweep start (ms); null until first pass. */
|
|
1395
|
+
let lastFlatSweepMs = null;
|
|
1396
|
+
|
|
1392
1397
|
async function reconcile(state) {
|
|
1393
1398
|
// Defence in depth — tickQueue already gates on this, but reconcile is the
|
|
1394
1399
|
// function that would do the damage (every unmatched PRD .md becomes a
|
|
@@ -1414,8 +1419,17 @@ async function reconcile(state) {
|
|
|
1414
1419
|
// instead of ever reaching the onDisk scan below.
|
|
1415
1420
|
let phaseStartMs = Date.now();
|
|
1416
1421
|
const skipCwds = quarantinedCwdSet(state);
|
|
1417
|
-
|
|
1418
|
-
|
|
1422
|
+
// Throttled: the flat layout is retired, and the sweep (plus the ~270 ms
|
|
1423
|
+
// allProjectCwds() scan) cost ~1 s per pass. A PRD dropped into a flat dir
|
|
1424
|
+
// is still consolidated within one FLAT_SWEEP_INTERVAL_MS; the first pass
|
|
1425
|
+
// after boot always runs.
|
|
1426
|
+
if (shouldRunFlatSweep(phaseStartMs, lastFlatSweepMs, FLAT_SWEEP_INTERVAL_MS)) {
|
|
1427
|
+
lastFlatSweepMs = phaseStartMs;
|
|
1428
|
+
await prdPaths.consolidateAllFlatPrds(allProjectCwds(), skipCwds);
|
|
1429
|
+
phaseMs.flatPrdSweep = Date.now() - phaseStartMs;
|
|
1430
|
+
} else {
|
|
1431
|
+
phaseMs.flatPrdSweep = 0;
|
|
1432
|
+
}
|
|
1419
1433
|
|
|
1420
1434
|
phaseStartMs = Date.now();
|
|
1421
1435
|
const { files, dirCount } = await listPrdFiles(skipCwds);
|
|
@@ -2048,18 +2062,34 @@ function getNextResetCached() {
|
|
|
2048
2062
|
* (usageCircuit.isResetFresh): a stale reset must never arm a resume timer
|
|
2049
2063
|
* that already elapsed (that's the 30-second-nap bug computeEffectiveResumeAt
|
|
2050
2064
|
* below also guards against), so a stale value here returns null and lets
|
|
2051
|
-
* the caller's 30-minute fallback take over instead.
|
|
2065
|
+
* the caller's 30-minute fallback take over instead. The billing fallback is
|
|
2066
|
+
* used only when its binding reading is at/above `threshold` — a 58% weekly
|
|
2067
|
+
* reading once paused the queue to the weekly reset 4 days out (2026-10-11);
|
|
2068
|
+
* an unknown/non-finite reading returns null so the 30-minute fallback applies.
|
|
2052
2069
|
*/
|
|
2053
|
-
function resolveRateLimitPauseReset(logPath, billingResetIso, nowMs = Date.now()) {
|
|
2070
|
+
function resolveRateLimitPauseReset(logPath, billingResetIso, nowMs = Date.now(), { billingUtilization, threshold } = {}) {
|
|
2054
2071
|
const logReset = resolveBindingRateLimitReset(logPath);
|
|
2055
2072
|
if (logReset != null) {
|
|
2056
2073
|
const iso = new Date(logReset * 1000).toISOString();
|
|
2057
2074
|
if (isResetFresh(iso, nowMs)) return iso;
|
|
2058
2075
|
}
|
|
2059
|
-
if (
|
|
2076
|
+
if (
|
|
2077
|
+
billingResetIso
|
|
2078
|
+
&& Number.isFinite(billingUtilization)
|
|
2079
|
+
&& Number.isFinite(threshold)
|
|
2080
|
+
&& billingUtilization >= threshold
|
|
2081
|
+
&& isResetFresh(billingResetIso, nowMs)
|
|
2082
|
+
) return billingResetIso;
|
|
2060
2083
|
return null;
|
|
2061
2084
|
}
|
|
2062
2085
|
|
|
2086
|
+
/** Logs when a fresh billing reset was discarded because its reading is under threshold. */
|
|
2087
|
+
function logBillingResetRejected(billingResetIso, billingUtilization, threshold) {
|
|
2088
|
+
if (!billingResetIso || !Number.isFinite(threshold)) return;
|
|
2089
|
+
if (Number.isFinite(billingUtilization) && billingUtilization >= threshold) return;
|
|
2090
|
+
console.log(`[scheduler] rate_limit pause: billing binding window at ${Number.isFinite(billingUtilization) ? `${billingUtilization}%` : 'unknown'} < ${threshold}% — using 30-min fallback, not ${billingResetIso}`);
|
|
2091
|
+
}
|
|
2092
|
+
|
|
2063
2093
|
/**
|
|
2064
2094
|
* Shared by both rate-limit pause sites (spawnJob's rateLimited branch and
|
|
2065
2095
|
* reapDeadRunningJobs): the billing-endpoint-derived fallback reset used
|
|
@@ -6077,7 +6107,10 @@ async function spawnJob(job, runId, runDir, defaultCwd, resumeTarget = null, sib
|
|
|
6077
6107
|
billing.usageCircuit.recordFailure('executor_429');
|
|
6078
6108
|
const logPath = path.join(runDir, `${job.slug}.log`);
|
|
6079
6109
|
const billingResetIso = await billingResetForPause();
|
|
6080
|
-
const
|
|
6110
|
+
const pauseState = await readQueue().catch(() => null);
|
|
6111
|
+
const pauseThreshold = pauseState?.config?.utilizationThreshold;
|
|
6112
|
+
const resetIso = resolveRateLimitPauseReset(logPath, billingResetIso, Date.now(), { billingUtilization: cachedUtilization, threshold: pauseThreshold });
|
|
6113
|
+
if (resetIso == null) logBillingResetRejected(billingResetIso, cachedUtilization, pauseThreshold);
|
|
6081
6114
|
const observedAt = dispatchStartedAtMs;
|
|
6082
6115
|
const prevCount = consecutiveRapidRateLimitsBySlug.get(job.slug) || 0;
|
|
6083
6116
|
const nextCount = nextRapidRateLimitCount(prevCount, { rateLimited: true, durationMs: res.durationMs });
|
|
@@ -6570,9 +6603,7 @@ async function spawnJob(job, runId, runDir, defaultCwd, resumeTarget = null, sib
|
|
|
6570
6603
|
// reason — surface that on the completed row's statusHistory
|
|
6571
6604
|
// instead of the generic "run finished with exit 0" every other
|
|
6572
6605
|
// completed run gets.
|
|
6573
|
-
const finalizeReason = (effectiveStatus
|
|
6574
|
-
? verifyResult.reason
|
|
6575
|
-
: (sigtermOverrideReason ?? `run finished with exit ${res.exitCode}`);
|
|
6606
|
+
const finalizeReason = finalizeReasonFor({ effectiveStatus, verifyResult, sigtermOverrideReason, exitCode: res.exitCode });
|
|
6576
6607
|
// error/verifierVerdict are stamped BEFORE transitionJob() below —
|
|
6577
6608
|
// needsReviewLedger's buildNeedsReviewEntryLine reads job.
|
|
6578
6609
|
// verifierVerdict/heldReason/error synchronously off `job` the
|
|
@@ -7875,7 +7906,9 @@ async function reapDeadRunningJobs() {
|
|
|
7875
7906
|
billing.usageCircuit.recordFailure('executor_429');
|
|
7876
7907
|
const triggering = dead.find((d) => d.outcome === 'rate_limited');
|
|
7877
7908
|
const billingResetIso = await billingResetForPause();
|
|
7878
|
-
const
|
|
7909
|
+
const pauseThreshold = state.config?.utilizationThreshold;
|
|
7910
|
+
const resetIso = resolveRateLimitPauseReset(triggering.logPath, billingResetIso, Date.now(), { billingUtilization: cachedUtilization, threshold: pauseThreshold });
|
|
7911
|
+
if (resetIso == null) logBillingResetRejected(billingResetIso, cachedUtilization, pauseThreshold);
|
|
7879
7912
|
const triggeringRow = triggering ? state.jobs.find((x) => x.slug === triggering.slug) : null;
|
|
7880
7913
|
const observedAtMs = triggeringRow?.startedAt ? Date.parse(triggeringRow.startedAt) : null;
|
|
7881
7914
|
// Same rapid-repeat circuit breaker spawnJob's own res.rateLimited
|
package/src/main/supervisor.cjs
CHANGED
|
@@ -205,6 +205,41 @@ Decision rules:
|
|
|
205
205
|
- "stuck"/kill-agent only if the agent itself is wedged (no child processes, no recent log events, > 60 min stale). Set targetPid to the claude root pid.`;
|
|
206
206
|
}
|
|
207
207
|
|
|
208
|
+
/**
|
|
209
|
+
* Parse `claude -p --output-format json` stdout from a probe.
|
|
210
|
+
* @param {string} stdout
|
|
211
|
+
* @returns {{ verdictObj: any, costUsd: number|null, errorSubtype: string|null }}
|
|
212
|
+
*/
|
|
213
|
+
function parseProbeOutput(stdout) {
|
|
214
|
+
let costUsd = null;
|
|
215
|
+
let verdictObj = null;
|
|
216
|
+
let errorSubtype = null;
|
|
217
|
+
|
|
218
|
+
// --output-format json emits a single JSON result object.
|
|
219
|
+
// The `result` field contains the assistant's raw text response.
|
|
220
|
+
for (const line of String(stdout).split('\n').reverse()) {
|
|
221
|
+
const trimmed = line.trim();
|
|
222
|
+
if (!trimmed) continue;
|
|
223
|
+
try {
|
|
224
|
+
const obj = JSON.parse(trimmed);
|
|
225
|
+
if (!obj || typeof obj !== 'object') continue;
|
|
226
|
+
if (typeof obj.total_cost_usd === 'number') costUsd = obj.total_cost_usd;
|
|
227
|
+
if (obj.is_error === true || (typeof obj.subtype === 'string' && obj.subtype !== 'success')) {
|
|
228
|
+
errorSubtype = typeof obj.subtype === 'string' && obj.subtype ? obj.subtype : 'error';
|
|
229
|
+
}
|
|
230
|
+
// Extract verdict from the result text field (assistant's response).
|
|
231
|
+
if (obj.result && typeof obj.result === 'string') {
|
|
232
|
+
const m = obj.result.match(/\{[^{}]+\}/);
|
|
233
|
+
if (m) {
|
|
234
|
+
try { verdictObj = JSON.parse(m[0]); } catch { /* */ }
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
} catch { /* not JSON */ }
|
|
238
|
+
if (verdictObj) break;
|
|
239
|
+
}
|
|
240
|
+
return { verdictObj, costUsd, errorSubtype };
|
|
241
|
+
}
|
|
242
|
+
|
|
208
243
|
async function runProbe(claudeBin, prompt) {
|
|
209
244
|
await ensureCliCapsProbed();
|
|
210
245
|
return new Promise((resolve) => {
|
|
@@ -213,10 +248,10 @@ async function runProbe(claudeBin, prompt) {
|
|
|
213
248
|
const target = claudeSpawnTarget('aux', 'supervisor', claudeBin);
|
|
214
249
|
child = spawn(target.command, [
|
|
215
250
|
'-p', prompt,
|
|
216
|
-
'--model', 'claude-
|
|
251
|
+
'--model', 'claude-haiku-5-5',
|
|
217
252
|
'--no-session-persistence',
|
|
218
253
|
'--output-format', 'json',
|
|
219
|
-
'--max-budget-usd', '0.
|
|
254
|
+
'--max-budget-usd', '0.50',
|
|
220
255
|
'--dangerously-skip-permissions',
|
|
221
256
|
...headlessPermissionArgs(),
|
|
222
257
|
'--allowedTools', 'Bash',
|
|
@@ -254,26 +289,13 @@ async function runProbe(claudeBin, prompt) {
|
|
|
254
289
|
child.on('exit', () => {
|
|
255
290
|
clearTimers();
|
|
256
291
|
const stdout = Buffer.concat(stdoutChunks).toString('utf8');
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
if (!trimmed) continue;
|
|
265
|
-
try {
|
|
266
|
-
const obj = JSON.parse(trimmed);
|
|
267
|
-
if (typeof obj.total_cost_usd === 'number') costUsd = obj.total_cost_usd;
|
|
268
|
-
// Extract verdict from the result text field (assistant's response).
|
|
269
|
-
if (obj.result && typeof obj.result === 'string') {
|
|
270
|
-
const m = obj.result.match(/\{[^{}]+\}/);
|
|
271
|
-
if (m) {
|
|
272
|
-
try { verdictObj = JSON.parse(m[0]); } catch { /* */ }
|
|
273
|
-
}
|
|
274
|
-
}
|
|
275
|
-
} catch { /* not JSON */ }
|
|
276
|
-
if (verdictObj) break;
|
|
292
|
+
const { verdictObj, costUsd, errorSubtype } = parseProbeOutput(stdout);
|
|
293
|
+
|
|
294
|
+
if ((!verdictObj || !verdictObj.verdict) && errorSubtype) {
|
|
295
|
+
// Budget cap / CLI error exit: record it distinctly, never as a real verdict.
|
|
296
|
+
console.warn('[supervisor] probe error exit (treating as ok):', errorSubtype);
|
|
297
|
+
resolve({ verdict: 'ok', action: 'none', targetPid: null, reason: `probe_error:${errorSubtype}`, costUsd });
|
|
298
|
+
return;
|
|
277
299
|
}
|
|
278
300
|
|
|
279
301
|
if (!verdictObj || !verdictObj.verdict) {
|
|
@@ -506,4 +528,5 @@ module.exports = {
|
|
|
506
528
|
supervisorTick,
|
|
507
529
|
applyAction,
|
|
508
530
|
registerHandlers,
|
|
531
|
+
parseProbeOutput,
|
|
509
532
|
};
|
package/src/main/webRemote.cjs
CHANGED
|
@@ -83,6 +83,7 @@ let _pongTimer = null;
|
|
|
83
83
|
let _missedPongs = 0;
|
|
84
84
|
let _configCache = null;
|
|
85
85
|
let _configCacheAt = 0;
|
|
86
|
+
let _repairNeeded = false; // relay rejected the device token (invalid_token / 401): terminal until re-pair
|
|
86
87
|
let _destroyed = false; // set at app shutdown to stop reconnect loops
|
|
87
88
|
|
|
88
89
|
// E2E session state — reset on each new WS connection.
|
|
@@ -274,6 +275,7 @@ function computeStatus(cfg) {
|
|
|
274
275
|
enabled: cfg.remoteEnabled,
|
|
275
276
|
remoteControlEnabled: cfg.remoteControlEnabled ?? false,
|
|
276
277
|
connected,
|
|
278
|
+
linkState: _repairNeeded ? 'repair-needed' : 'ok',
|
|
277
279
|
e2eActive: connected && _clients.values().some((r) => r.e2e.sessionKey !== null),
|
|
278
280
|
e2eAuthenticated: connected && authed !== null,
|
|
279
281
|
e2eState: connected ? e2eState : 'idle',
|
|
@@ -389,9 +391,18 @@ async function connect() {
|
|
|
389
391
|
scope: 'webRemote', level: 'warn',
|
|
390
392
|
message: 'device ticket request failed', meta: { error: e?.message },
|
|
391
393
|
});
|
|
394
|
+
// invalid_token / 401 is final (device unpaired server-side): retrying only
|
|
395
|
+
// spams the relay. Park in repair-needed until a new pair stores a fresh token.
|
|
396
|
+
if (e?.message === 'invalid_token' || e?.message === 'HTTP 401') {
|
|
397
|
+
cancelReconnect();
|
|
398
|
+
_repairNeeded = true;
|
|
399
|
+
broadcastStatus();
|
|
400
|
+
return;
|
|
401
|
+
}
|
|
392
402
|
scheduleReconnect();
|
|
393
403
|
return;
|
|
394
404
|
}
|
|
405
|
+
_repairNeeded = false;
|
|
395
406
|
|
|
396
407
|
// Step 2: open WSS connection with the ticket.
|
|
397
408
|
let ws;
|
|
@@ -1278,6 +1289,7 @@ async function pair(otp) {
|
|
|
1278
1289
|
}];
|
|
1279
1290
|
|
|
1280
1291
|
await saveConfig({ ...cfg, devices });
|
|
1292
|
+
_repairNeeded = false;
|
|
1281
1293
|
|
|
1282
1294
|
if (cfg.remoteEnabled) {
|
|
1283
1295
|
connect().catch(() => {});
|
|
@@ -1480,6 +1492,9 @@ module.exports = {
|
|
|
1480
1492
|
getDispatchMap,
|
|
1481
1493
|
buildRemoteSessionList,
|
|
1482
1494
|
setWs: (ws) => { _ws = ws; },
|
|
1495
|
+
connect,
|
|
1496
|
+
hasReconnectTimer: () => _reconnectTimer !== null,
|
|
1497
|
+
cancelReconnect,
|
|
1483
1498
|
invalidateConfigCache,
|
|
1484
1499
|
setE2eAuthenticated: (key, clientKey = LEGACY_CLIENT_KEY) => {
|
|
1485
1500
|
_clients.getOrCreate(clientKey).e2e = makeState('authenticated', key, null);
|
package/src/preload/api.d.ts
CHANGED
|
@@ -1259,9 +1259,41 @@ export interface MemoryEntry {
|
|
|
1259
1259
|
bytes: number;
|
|
1260
1260
|
}
|
|
1261
1261
|
|
|
1262
|
+
export interface MemoryAgentTarget {
|
|
1263
|
+
name: string;
|
|
1264
|
+
scope: 'user' | 'project' | 'local';
|
|
1265
|
+
}
|
|
1266
|
+
|
|
1267
|
+
/** Addressing for memory:* calls. `agent` targets an agent's native memory dir; `cwd` drives native resolution. */
|
|
1268
|
+
export interface MemoryTarget {
|
|
1269
|
+
workspace?: string;
|
|
1270
|
+
cwd?: string;
|
|
1271
|
+
agent?: MemoryAgentTarget;
|
|
1272
|
+
}
|
|
1273
|
+
|
|
1274
|
+
export interface MemoryAgent {
|
|
1275
|
+
name: string;
|
|
1276
|
+
origin: 'user' | 'project';
|
|
1277
|
+
memoryScope: 'user' | 'project' | 'local' | null;
|
|
1278
|
+
agentFile: string;
|
|
1279
|
+
}
|
|
1280
|
+
|
|
1281
|
+
export interface MemoryIndexHealth {
|
|
1282
|
+
lines: number;
|
|
1283
|
+
bytes: number;
|
|
1284
|
+
maxLines: number;
|
|
1285
|
+
maxBytes: number;
|
|
1286
|
+
overBudget: boolean;
|
|
1287
|
+
nearBudget: boolean;
|
|
1288
|
+
orphans: string[];
|
|
1289
|
+
dangling: string[];
|
|
1290
|
+
}
|
|
1291
|
+
|
|
1262
1292
|
export interface MemoryListResult {
|
|
1263
1293
|
entries: MemoryEntry[];
|
|
1264
1294
|
workspace: string;
|
|
1295
|
+
dir: string;
|
|
1296
|
+
autoMemoryEnabled: boolean;
|
|
1265
1297
|
error: string | null;
|
|
1266
1298
|
}
|
|
1267
1299
|
|
|
@@ -1314,6 +1346,7 @@ export interface MemoryStaleResult {
|
|
|
1314
1346
|
entries: MemoryStaleEntry[];
|
|
1315
1347
|
workspace: string;
|
|
1316
1348
|
error: string | null;
|
|
1349
|
+
index?: MemoryIndexHealth;
|
|
1317
1350
|
}
|
|
1318
1351
|
|
|
1319
1352
|
// ────────────────────────────────────────────── Project Brief (PRD 837)
|
|
@@ -1402,37 +1435,6 @@ export interface ProjectPagesGetResult {
|
|
|
1402
1435
|
demoVideo: { path: string; mtimeMs: number } | null;
|
|
1403
1436
|
}
|
|
1404
1437
|
|
|
1405
|
-
// ────────────────────────────────────────────── Per-subagent memory
|
|
1406
|
-
// Stored at ~/.claude/session-manager/agent-memory/<agentId>.json. Keyed by
|
|
1407
|
-
// agent name (the .md filename in ~/.claude/agents/), not by workspace cwd.
|
|
1408
|
-
|
|
1409
|
-
export type AgentMemoryCategory = 'command' | 'preference' | 'pattern' | 'failure' | 'workflow';
|
|
1410
|
-
|
|
1411
|
-
export interface AgentMemoryEntry {
|
|
1412
|
-
id: string;
|
|
1413
|
-
body: string;
|
|
1414
|
-
category: AgentMemoryCategory | null;
|
|
1415
|
-
createdAt: number;
|
|
1416
|
-
updatedAt: number;
|
|
1417
|
-
bytes: number;
|
|
1418
|
-
}
|
|
1419
|
-
|
|
1420
|
-
export interface AgentMemoryListResult {
|
|
1421
|
-
entries: AgentMemoryEntry[];
|
|
1422
|
-
agentId: string;
|
|
1423
|
-
error: string | null;
|
|
1424
|
-
}
|
|
1425
|
-
|
|
1426
|
-
export interface AgentMemoryGetResult {
|
|
1427
|
-
entry: AgentMemoryEntry | null;
|
|
1428
|
-
error: string | null;
|
|
1429
|
-
}
|
|
1430
|
-
|
|
1431
|
-
export interface AgentMemoryMutationResult {
|
|
1432
|
-
ok: boolean;
|
|
1433
|
-
error: string | null;
|
|
1434
|
-
}
|
|
1435
|
-
|
|
1436
1438
|
// ────────────────────────────────────────────── Git status (richer than app:git-branch)
|
|
1437
1439
|
|
|
1438
1440
|
/** Mirrors the status returned by src/main/git.cjs mapStatus(). */
|
|
@@ -1495,6 +1497,8 @@ export interface ChatRunPayload {
|
|
|
1495
1497
|
/** Originating PromptTicket.id (PRD 748) when dequeued from a tab's
|
|
1496
1498
|
* prompt queue — absent for a fresh manual send. */
|
|
1497
1499
|
promptId?: string;
|
|
1500
|
+
/** Who wrote the prompt: a local human, an injected external source, or an Epic proposal from another project. */
|
|
1501
|
+
origin?: 'human' | 'external' | 'inbound-proposal';
|
|
1498
1502
|
}
|
|
1499
1503
|
|
|
1500
1504
|
export interface ChatCreatePrdPayload {
|
|
@@ -1649,6 +1653,8 @@ export interface WebRemoteStatus {
|
|
|
1649
1653
|
enabled: boolean;
|
|
1650
1654
|
remoteControlEnabled: boolean;
|
|
1651
1655
|
connected: boolean;
|
|
1656
|
+
/** 'repair-needed' once the relay rejected the device token; the device must be re-paired. */
|
|
1657
|
+
linkState?: 'ok' | 'repair-needed';
|
|
1652
1658
|
e2eActive: boolean;
|
|
1653
1659
|
/** True once the user has confirmed the SAS on the desktop. */
|
|
1654
1660
|
e2eAuthenticated: boolean;
|
|
@@ -2090,19 +2096,21 @@ export interface SessionManagerAPI {
|
|
|
2090
2096
|
};
|
|
2091
2097
|
memory: {
|
|
2092
2098
|
/** List markdown memory entries for the given workspace (defaults to 'default'). */
|
|
2093
|
-
list: (
|
|
2099
|
+
list: (target?: MemoryTarget) => Promise<MemoryListResult>;
|
|
2094
2100
|
/** Read the contents of one memory entry. */
|
|
2095
|
-
read: (name: string,
|
|
2101
|
+
read: (name: string, target?: MemoryTarget) => Promise<MemoryReadResult>;
|
|
2096
2102
|
/** Atomic write of a memory entry (max 1 MiB). */
|
|
2097
|
-
write: (name: string, content: string,
|
|
2103
|
+
write: (name: string, content: string, target?: MemoryTarget) => Promise<MemoryMutationResult>;
|
|
2098
2104
|
/** Delete a memory entry. */
|
|
2099
|
-
delete: (name: string,
|
|
2105
|
+
delete: (name: string, target?: MemoryTarget) => Promise<MemoryMutationResult>;
|
|
2100
2106
|
/** Create a new memory entry with starter frontmatter + body. */
|
|
2101
|
-
create: (name: string, description?: string,
|
|
2107
|
+
create: (name: string, description?: string, target?: MemoryTarget) => Promise<MemoryMutationResult>;
|
|
2102
2108
|
/** Aggregate workspace memories into semantic clusters. `refresh:true` fires a cost-gated `claude -p` pass; otherwise returns the cache only. */
|
|
2103
2109
|
aggregate: (workspace: string, refresh?: boolean, cwd?: string) => Promise<MemoryAggregateResult>;
|
|
2104
2110
|
/** Deterministic, zero-LLM-cost staleness report. `cwd` (optional) scopes the dead-repo-ref check. */
|
|
2105
|
-
stale: (
|
|
2111
|
+
stale: (target?: MemoryTarget) => Promise<MemoryStaleResult>;
|
|
2112
|
+
/** Agents (user + project) with their `memory:` frontmatter scope. */
|
|
2113
|
+
agents: (cwd?: string) => Promise<{ agents: MemoryAgent[] }>;
|
|
2106
2114
|
};
|
|
2107
2115
|
projectBrief: {
|
|
2108
2116
|
/** Read brief.json (or null if none yet) plus cheaply-computed source/drift metadata. Never fires an LLM call. */
|
|
@@ -2150,21 +2158,6 @@ export interface SessionManagerAPI {
|
|
|
2150
2158
|
fields: { cwd: string; epicId: string; source: string },
|
|
2151
2159
|
) => Promise<{ ok: boolean }>;
|
|
2152
2160
|
};
|
|
2153
|
-
agentMemory: {
|
|
2154
|
-
/** List all memory entries for one subagent. Sorted newest first. */
|
|
2155
|
-
list: (agentId: string) => Promise<AgentMemoryListResult>;
|
|
2156
|
-
/** Get one entry's full body. Returns `{entry:null}` if missing. */
|
|
2157
|
-
get: (agentId: string, entryId: string) => Promise<AgentMemoryGetResult>;
|
|
2158
|
-
/** Upsert one entry. Atomic write through config.cjs; max body 1 MiB. */
|
|
2159
|
-
set: (
|
|
2160
|
-
agentId: string,
|
|
2161
|
-
entryId: string,
|
|
2162
|
-
body: string,
|
|
2163
|
-
category?: AgentMemoryCategory,
|
|
2164
|
-
) => Promise<AgentMemoryMutationResult>;
|
|
2165
|
-
/** Delete one entry. Removes the file outright when last entry is removed. */
|
|
2166
|
-
delete: (agentId: string, entryId: string) => Promise<AgentMemoryMutationResult>;
|
|
2167
|
-
};
|
|
2168
2161
|
git: {
|
|
2169
2162
|
/** Full git status for `cwd`. Returns null when not a git repo, git is
|
|
2170
2163
|
* missing, or the call times out (5s ceiling). Cached per-cwd for 5s. */
|
|
@@ -2260,6 +2253,8 @@ export interface SessionManagerAPI {
|
|
|
2260
2253
|
}) => Promise<Exchange[]>;
|
|
2261
2254
|
};
|
|
2262
2255
|
promptSessions: {
|
|
2256
|
+
/** Main requests approve-and-start of a verified inbound proposal. */
|
|
2257
|
+
onInboundAutoApprove: (handler: (e: { cwd: string; epicId: string }) => void) => () => void;
|
|
2263
2258
|
/** Fires when the main process appends an event to a PromptSession's
|
|
2264
2259
|
* chain on disk (currently only the scheduler's response-event append
|
|
2265
2260
|
* from notifyOriginatingTab) — lets an already-hydrated Epic pick it up
|
package/src/preload/index.cjs
CHANGED
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
const { contextBridge, ipcRenderer } = require('electron');
|
|
2
2
|
|
|
3
|
+
/** Forward only the defined addressing keys of a memory target. */
|
|
4
|
+
function memoryTarget(target) {
|
|
5
|
+
const out = {};
|
|
6
|
+
if (!target || typeof target !== 'object') return out;
|
|
7
|
+
if (target.workspace) out.workspace = target.workspace;
|
|
8
|
+
if (target.cwd) out.cwd = target.cwd;
|
|
9
|
+
if (target.agent) out.agent = target.agent;
|
|
10
|
+
return out;
|
|
11
|
+
}
|
|
12
|
+
|
|
3
13
|
contextBridge.exposeInMainWorld('api', {
|
|
4
14
|
app: {
|
|
5
15
|
version: () => ipcRenderer.invoke('app:version'),
|
|
@@ -335,22 +345,20 @@ contextBridge.exposeInMainWorld('api', {
|
|
|
335
345
|
writeText: (text) => ipcRenderer.invoke('clipboard:write-text', { text }),
|
|
336
346
|
},
|
|
337
347
|
memory: {
|
|
338
|
-
list: (
|
|
339
|
-
read: (name,
|
|
340
|
-
write: (name, content,
|
|
341
|
-
ipcRenderer.invoke('memory:write',
|
|
342
|
-
delete: (name,
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
const payload = { name };
|
|
348
|
+
list: (target) => ipcRenderer.invoke('memory:list', memoryTarget(target)),
|
|
349
|
+
read: (name, target) => ipcRenderer.invoke('memory:read', { name, ...memoryTarget(target) }),
|
|
350
|
+
write: (name, content, target) =>
|
|
351
|
+
ipcRenderer.invoke('memory:write', { name, content, ...memoryTarget(target) }),
|
|
352
|
+
delete: (name, target) => ipcRenderer.invoke('memory:delete', { name, ...memoryTarget(target) }),
|
|
353
|
+
create: (name, description, target) => {
|
|
354
|
+
const payload = { name, ...memoryTarget(target) };
|
|
346
355
|
if (description) payload.description = description;
|
|
347
|
-
if (workspace) payload.workspace = workspace;
|
|
348
356
|
return ipcRenderer.invoke('memory:create', payload);
|
|
349
357
|
},
|
|
350
358
|
aggregate: (workspace, refresh, cwd) =>
|
|
351
359
|
ipcRenderer.invoke('memory:aggregate', { workspace, ...(refresh ? { refresh: true } : {}), ...(cwd ? { cwd } : {}) }),
|
|
352
|
-
stale: (
|
|
353
|
-
|
|
360
|
+
stale: (target) => ipcRenderer.invoke('memory:stale', memoryTarget(target)),
|
|
361
|
+
agents: (cwd) => ipcRenderer.invoke('memory:agents', cwd ? { cwd } : {}),
|
|
354
362
|
},
|
|
355
363
|
projectBrief: {
|
|
356
364
|
get: (cwd) => ipcRenderer.invoke('project-brief:get', { cwd }),
|
|
@@ -381,16 +389,6 @@ contextBridge.exposeInMainWorld('api', {
|
|
|
381
389
|
auditLog: {
|
|
382
390
|
append: (kind, fields) => ipcRenderer.invoke('auditLog:append', { kind, ...fields }),
|
|
383
391
|
},
|
|
384
|
-
agentMemory: {
|
|
385
|
-
list: (agentId) => ipcRenderer.invoke('agent-memory:list', { agentId }),
|
|
386
|
-
get: (agentId, entryId) => ipcRenderer.invoke('agent-memory:get', { agentId, entryId }),
|
|
387
|
-
set: (agentId, entryId, body, category) => {
|
|
388
|
-
const payload = { agentId, entryId, body };
|
|
389
|
-
if (category) payload.category = category;
|
|
390
|
-
return ipcRenderer.invoke('agent-memory:set', payload);
|
|
391
|
-
},
|
|
392
|
-
delete: (agentId, entryId) => ipcRenderer.invoke('agent-memory:delete', { agentId, entryId }),
|
|
393
|
-
},
|
|
394
392
|
git: {
|
|
395
393
|
// Returns null when cwd is not a git repo, git is missing, or the call
|
|
396
394
|
// times out (5s ceiling). The existing `app.gitBranch` is intentionally
|
|
@@ -543,6 +541,12 @@ contextBridge.exposeInMainWorld('api', {
|
|
|
543
541
|
list: (payload) => ipcRenderer.invoke('exchanges:list', payload),
|
|
544
542
|
},
|
|
545
543
|
promptSessions: {
|
|
544
|
+
/** Main asks the renderer to approve-and-start a verified inbound proposal. */
|
|
545
|
+
onInboundAutoApprove: (handler) => {
|
|
546
|
+
const listener = (_e, payload) => handler(payload);
|
|
547
|
+
ipcRenderer.on('promptSessions:inbound-auto-approve', listener);
|
|
548
|
+
return () => ipcRenderer.removeListener('promptSessions:inbound-auto-approve', listener);
|
|
549
|
+
},
|
|
546
550
|
/** Fires when the main process (currently only the scheduler's
|
|
547
551
|
* response-event append) appends an event to a PromptSession's chain on
|
|
548
552
|
* disk — lets an already-hydrated Epic pick it up live instead of
|
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
import{b2 as I,aW as K,aE as i,aV as A,ao as e,n as F,Y as oe,ac as H,a4 as le,ag as V}from"./index-CTBpf-26.js";import{P as q}from"./Panel-_VZ_uXNg.js";import{L as ee}from"./ListDetail-PStOuQAo.js";import{M as te}from"./MarkdownEditor-DPw-N2-m.js";import{V as se}from"./ViewTabs-9lQCRwWJ.js";import"./monaco-editor-BsIlgG7c.js";function ie(t){return!t||!t.trim()?"default":t.replace(/[^a-zA-Z0-9]/g,"-")}const re=/^[a-z0-9-_]+$/;function ce(t,l,n,o){return t.filter(x=>!l||x.name.toLowerCase().includes(l.toLowerCase())).filter(x=>{var s;return!n||((s=o[x.name])==null?void 0:s.stale)===!0})}const de=["Commands:"," list — show all memory entries"," show <slug> — read one entry"," search <text> — find entries containing <text>"," forget <slug> — delete an entry (with confirm)"," remember <free-form> — ask Claude to save this"," help — this message","","Anything else: I will offer to send it to Claude in the terminal,","or add it as a new memory entry directly."].join(`
|
|
2
|
-
`);function me(t){const l=t.trim();if(!l)return{kind:"freeform",arg:"",raw:t};const n=l.toLowerCase();if(n==="help"||n==="?")return{kind:"help",arg:"",raw:t};if(n==="list"||n==="ls"||n==="list memories"||n==="list all"||n==="what do you remember"||n==="what do you remember?")return{kind:"list",arg:"",raw:t};const o=l.match(/^(?:show|read|open)\s+(.+)$/i);if(o)return{kind:"show",arg:o[1].trim(),raw:t};const x=l.match(/^(?:forget|delete|remove)\s+(.+)$/i);if(x)return{kind:"forget",arg:x[1].trim(),raw:t};const s=l.match(/^(?:search|find)\s+(.+)$/i)||l.match(/^what (?:do )?you know about\s+(.+?)\??$/i);if(s)return{kind:"search",arg:s[1].trim(),raw:t};const f=l.match(/^remember(?:\s+that)?\s+(.+)$/i)||l.match(/^note(?:\s+that)?\s+(.+)$/i)||l.match(/^save(?:\s+that)?\s+(.+)$/i);return f?{kind:"remember",arg:f[1].trim(),raw:t}:{kind:"freeform",arg:l,raw:t}}function G(t,l){const n=t.toLowerCase().replace(/\.md$/,""),o=l.find(f=>f.name.replace(/\.md$/,"").toLowerCase()===n);if(o)return o;const x=l.filter(f=>f.name.replace(/\.md$/,"").toLowerCase().startsWith(n));if(x.length===1)return x[0];const s=l.filter(f=>f.name.replace(/\.md$/,"").toLowerCase().includes(n));return s.length===1?s[0]:null}function Z(t){const l=new Set(["the","a","an","is","are","to","of","in","for","and","or"]);return t.toLowerCase().replace(/[^a-z0-9\s-]/g," ").split(/\s+/).filter(x=>x&&!l.has(x)).slice(0,4).join("-").replace(/-+/g,"-").replace(/^-|-$/g,"")||`note-${Date.now().toString(36).slice(-4)}`}function Q(t,l){return[`Please save this to my workspace memory (workspace: ${t}).`,`Use the memory tool (or write a markdown file under ~/.claude/projects/${t}/memory/).`,"Pick a short kebab-case filename and add a one-line title at the top.","","Memory to save:",l].join(`
|
|
3
|
-
`)}function X(){return`${Date.now().toString(36)}-${Math.random().toString(36).slice(2,8)}`}function xe({workspace:t,entries:l,onChanged:n}){const o=I(),x=K(),s=(x==null?void 0:x.id)??null,[f,u]=i.useState(""),[d,b]=i.useState(()=>[{id:X(),role:"system",text:"Hi — I am the memory assistant for this workspace. Type `help` for what I understand, or just talk to me."}]),[C,g]=i.useState(!1),j=i.useRef(null),L=i.useRef(null);i.useEffect(()=>{const a=j.current;a&&(a.scrollTop=a.scrollHeight)},[d]);const c=i.useCallback(a=>{b(p=>[...p,{...a,id:X()}])},[]),$=i.useCallback(()=>{if(l.length===0){c({role:"result",text:"No memories saved for this workspace yet."});return}c({role:"result",text:`Found ${l.length} ${l.length===1?"memory":"memories"}:`,payload:{kind:"entries",entries:l}})},[l,c]),M=i.useCallback(async a=>{const p=G(a,l);if(!p){c({role:"result",text:`No memory matches "${a}". Try \`list\` to see what is here.`});return}const y=await window.api.memory.read(p.name,t);if(y.error){A.error(`Memory read failed: ${y.error}`),c({role:"result",text:`Read failed: ${y.error}`});return}c({role:"result",text:`${p.name}`,payload:{kind:"entry",entry:p,content:y.content}})},[l,t,c]),D=i.useCallback(async a=>{const p=a.toLowerCase(),y=l.filter(k=>k.name.toLowerCase().includes(p)),O=[];for(const k of l){if(y.includes(k))continue;const B=await window.api.memory.read(k.name,t);if(B.error)continue;const m=B.content.split(`
|
|
4
|
-
`),h=m.findIndex(N=>N.toLowerCase().includes(p));h>=0&&O.push({entry:k,snippet:m[h].trim().slice(0,200)})}const r=[...y.map(k=>({entry:k,snippet:"(filename match)"})),...O];if(r.length===0){c({role:"result",text:`No matches for "${a}".`});return}c({role:"result",text:`Found ${r.length} ${r.length===1?"match":"matches"} for "${a}":`,payload:{kind:"search",query:a,hits:r}})},[l,t,c]),R=i.useCallback(async a=>{const p=G(a,l);if(!p){c({role:"result",text:`No memory matches "${a}". Try \`list\` to see what is here.`});return}if(!window.confirm(`Delete memory "${p.name}"? This cannot be undone.`))return;const y=await window.api.memory.delete(p.name,t);if(!y.ok){A.error(`Memory delete failed: ${y.error??"unknown error"}`),c({role:"result",text:`Delete failed: ${y.error??"unknown error"}`});return}c({role:"result",text:`Deleted ${p.name}.`}),n()},[l,t,c,n]),w=i.useCallback(a=>{const p=Z(a),y=Q(t,a);c({role:"result",text:"I do not write to memory from this panel. Send the request to Claude in the active terminal, or add it directly as a new memory entry below.",payload:{kind:"suggest-send",prompt:y,original:a}}),c({role:"result",text:`Add it directly as \`${p}.md\` instead?`,payload:{kind:"suggest-add",slug:p,content:a}})},[t,c]),_=i.useCallback(a=>{const p=Z(a),y=Q(t,a);c({role:"result",text:"Not a recognised command. Options:",payload:{kind:"suggest-send",prompt:y,original:a}}),c({role:"result",text:`…or save it directly as \`${p}.md\`.`,payload:{kind:"suggest-add",slug:p,content:a}})},[t,c]),E=i.useCallback(async a=>{g(!0);try{switch(a.kind){case"list":$();break;case"show":await M(a.arg);break;case"search":await D(a.arg);break;case"forget":await R(a.arg);break;case"help":c({role:"result",text:de});break;case"remember":w(a.arg);break;case"freeform":_(a.arg);break}}finally{g(!1)}},[$,M,D,R,w,_,c]),T=i.useCallback(async()=>{const a=f.trim();!a||C||(u(""),c({role:"user",text:a}),await E(me(a)))},[f,C,E,c]),v=a=>{a.key==="Enter"&&!a.shiftKey&&(a.preventDefault(),T())},S=i.useCallback(a=>{if(!s){A.error("No active terminal session — open or focus a tab first.");return}window.api.pty.write({tabId:s,data:a}),A.info("Prompt sent to active terminal (not submitted)")},[s]),P=i.useCallback(async(a,p)=>{if(!re.test(a)){A.error(`Invalid slug "${a}" — must be [a-z0-9-_]+`);return}const y=`${a}.md`,O=await window.api.memory.create(y,void 0,t);if(!O.ok){A.error(`Create failed: ${O.error??"unknown error"}`);return}const r=await window.api.memory.read(y,t),k=r.error?p:`${r.content.trimEnd()}
|
|
5
|
-
|
|
6
|
-
${p}
|
|
7
|
-
`,B=await window.api.memory.write(y,k,t);if(!B.ok){A.error(`Write failed: ${B.error??"unknown error"}`);return}c({role:"result",text:`Saved as ${y}.`}),n()},[t,c,n]),W=i.useMemo(()=>[{label:"List memories",value:"list"},{label:"Help",value:"help"},{label:"Search…",value:"search "},{label:"Remember…",value:"remember "}],[]);return o?e.jsxs("div",{className:"h-full flex flex-col",children:[e.jsxs("div",{ref:j,className:"flex-1 min-h-0 overflow-y-auto px-3 py-2 space-y-2",children:[d.map(a=>e.jsx(fe,{message:a,entries:l,onShow:p=>{var y;u(`show ${p.replace(/\.md$/,"")}`),(y=L.current)==null||y.focus()},onSendToClaude:S,onAddDirect:P},a.id)),C&&e.jsx("div",{className:"text-xs text-fg-faint italic",children:"working…"})]}),e.jsxs("div",{className:"shrink-0 border-t border-line px-3 py-2 space-y-2",children:[e.jsx("div",{className:"flex gap-1 flex-wrap",children:W.map(a=>e.jsx("button",{onClick:()=>{var p;u(a.value),(p=L.current)==null||p.focus()},className:"px-2 py-0.5 text-[11px] text-fg-faint hover:text-fg-dim border border-line hover:border-fg-faint rounded transition-colors",children:a.label},a.label))}),e.jsxs("div",{className:"flex gap-2 items-end",children:[e.jsx(oe,{}),e.jsx("textarea",{ref:L,value:f,onChange:a=>u(a.target.value),onKeyDown:v,placeholder:"Type `list`, `show <slug>`, `search <text>`, `remember …`, or talk freely. Enter to send, Shift+Enter for newline.",rows:2,className:"flex-1 bg-bg border border-line rounded px-2 py-1 text-xs text-fg resize-none focus:outline-none focus:border-fg-faint"}),e.jsx("button",{onClick:T,disabled:!f.trim()||C,className:"px-3 py-1 text-xs text-fg border border-line hover:bg-bg-hi rounded transition-colors disabled:opacity-40 disabled:cursor-not-allowed",children:"Send"})]}),e.jsxs("div",{className:"text-[10px] text-fg-faint",children:["Workspace: ",e.jsx("span",{className:"font-mono",children:t}),!s&&e.jsx("span",{className:"ml-2 text-amber-400",children:"(no active terminal — Send-to-Claude disabled)"})]})]})]}):e.jsx(F,{title:"loading…"})}function fe({message:t,entries:l,onShow:n,onSendToClaude:o,onAddDirect:x}){var f,u,d,b,C;const s=t.role==="user"?"bg-bg-hi text-fg ml-8":t.role==="system"?"bg-bg text-fg-dim italic":"bg-bg text-fg-dim";return e.jsxs("div",{className:`text-xs rounded border border-line px-3 py-2 ${s}`,children:[e.jsx("div",{className:"whitespace-pre-wrap font-mono text-[11px] leading-relaxed",children:t.text}),((f=t.payload)==null?void 0:f.kind)==="entries"&&e.jsx("div",{className:"mt-2 space-y-0.5",children:t.payload.entries.map(g=>e.jsxs("button",{onClick:()=>n(g.name),className:"block w-full text-left px-2 py-0.5 text-[11px] text-fg-dim hover:text-fg hover:bg-bg-hi rounded font-mono",children:[g.name," ",e.jsxs("span",{className:"text-fg-faint",children:["(",H(g.bytes),")"]})]},g.name))}),((u=t.payload)==null?void 0:u.kind)==="entry"&&e.jsx("pre",{className:"mt-2 max-h-64 overflow-auto px-2 py-1 bg-bg-hi rounded text-[11px] whitespace-pre-wrap text-fg",children:t.payload.content}),((d=t.payload)==null?void 0:d.kind)==="search"&&e.jsx("div",{className:"mt-2 space-y-1",children:t.payload.hits.map((g,j)=>e.jsxs("button",{onClick:()=>n(g.entry.name),className:"block w-full text-left px-2 py-1 hover:bg-bg-hi rounded",children:[e.jsx("div",{className:"font-mono text-[11px] text-fg-dim",children:g.entry.name}),e.jsx("div",{className:"text-[10px] text-fg-faint truncate",children:g.snippet})]},`${g.entry.name}-${j}`))}),((b=t.payload)==null?void 0:b.kind)==="suggest-send"&&e.jsxs("div",{className:"mt-2 flex gap-2 items-center",children:[e.jsx("button",{onClick:()=>o(t.payload.kind==="suggest-send"?t.payload.prompt:""),className:"px-2 py-0.5 text-[11px] text-fg-dim hover:text-fg border border-line hover:border-fg-faint rounded transition-colors",children:"Send to Claude in terminal"}),e.jsx("span",{className:"text-[10px] text-fg-faint",children:"paste-only — you press Enter"})]}),((C=t.payload)==null?void 0:C.kind)==="suggest-add"&&e.jsxs("div",{className:"mt-2 flex gap-2 items-center",children:[e.jsx("button",{onClick:()=>{var g;return((g=t.payload)==null?void 0:g.kind)==="suggest-add"&&x(t.payload.slug,t.payload.content)},className:"px-2 py-0.5 text-[11px] text-fg-dim hover:text-fg border border-line hover:border-fg-faint rounded transition-colors",children:"Add as new memory"}),e.jsxs("span",{className:"text-[10px] text-fg-faint font-mono",children:[t.payload.slug,".md"]})]})]})}const z=le((t,l)=>({entriesByAgent:new Map,loading:null,errorByAgent:new Map,loadAgent:async n=>{t({loading:n});const o=await window.api.agentMemory.list(n),x=new Map(l().entriesByAgent);x.set(n,o.entries);const s=new Map(l().errorByAgent);o.error?(s.set(n,o.error),A.error(`Agent memory load failed: ${o.error}`)):s.delete(n),t({entriesByAgent:x,loading:null,errorByAgent:s})},setEntry:async(n,o,x,s)=>{const f=await window.api.agentMemory.set(n,o,x,s);return f.ok&&await l().loadAgent(n),f},deleteEntry:async(n,o)=>{const x=await window.api.agentMemory.delete(n,o);if(x.ok){const s=new Map(l().entriesByAgent),f=s.get(n)??[];s.set(n,f.filter(u=>u.id!==o)),t({entriesByAgent:s})}return x}})),Y=/^[A-Za-z0-9._-]{1,128}$/,U={command:"Commands",preference:"Preferences",pattern:"Patterns",failure:"Avoid",workflow:"Workflow"},ne=["command","preference","pattern","failure","workflow"];async function ue(t,l){const n=[{dir:`${t}/.claude/agents`,scope:"user"}];l&&n.push({dir:`${l}/.claude/agents`,scope:"project"});const o=[];for(const{dir:s,scope:f}of n)try{const u=await window.api.config.listDir(s,{filesOnly:!0});for(const d of u.entries){if(!d.name.endsWith(".md"))continue;const b=d.name.replace(/\.md$/,"");Y.test(b)&&o.push({id:b,label:b,scope:f})}}catch{}const x=new Map;for(const s of o)s.scope==="project"?x.set(s.id,s):x.has(s.id)||x.set(s.id,s);return Array.from(x.values()).sort((s,f)=>s.id.localeCompare(f.id))}function he(){const t=I(),l=K(),n=(l==null?void 0:l.cwd)??null,[o,x]=i.useState([]),[s,f]=i.useState(null),[u,d]=i.useState(null),[b,C]=i.useState(""),[g,j]=i.useState(!1),[L,c]=i.useState(!1),[$,M]=i.useState({}),D=z(r=>r.entriesByAgent),R=z(r=>r.loadAgent),w=z(r=>r.setEntry),_=z(r=>r.deleteEntry);i.useEffect(()=>{if(!t)return;let r=!1;return ue(t,n).then(k=>{r||(x(k),f(B=>{var m;return B&&k.find(h=>h.id===B)?B:((m=k[0])==null?void 0:m.id)??null}))}),()=>{r=!0}},[t,n]),i.useEffect(()=>{s&&(d(null),R(s))},[s,R]);const E=s?D.get(s)??[]:[],T=i.useMemo(()=>E.filter(r=>!b||r.id.toLowerCase().includes(b.toLowerCase())||r.body.toLowerCase().includes(b.toLowerCase())),[E,b]),v=i.useMemo(()=>E.find(r=>r.id===u)??null,[E,u]);i.useEffect(()=>{var r;!u&&T.length>0?d(T[0].id):u&&!T.find(k=>k.id===u)&&d(((r=T[0])==null?void 0:r.id)??null)},[T,u]);const S=s&&u?`${s}::${u}`:null,P=S?$[S]??(v==null?void 0:v.body)??"":"",W=!!S&&v!==null&&P!==v.body,a=i.useCallback(async()=>{s&&await R(s)},[s,R]),p=async()=>{if(!s||!u||!S)return;j(!0);const r=await w(s,u,P,(v==null?void 0:v.category)??void 0);if(j(!1),!r.ok){A.error(`Save failed: ${r.error??"unknown"}`);return}M(k=>{const B={...k};return delete B[S],B})},y=async()=>{if(!s||!u||!window.confirm(`Delete memory "${u}"? This cannot be undone.`))return;j(!0);const r=await _(s,u);if(j(!1),!r.ok){A.error(`Delete failed: ${r.error??"unknown"}`);return}d(null)},O=async r=>{if(!s||!u||!v)return;j(!0);const k=await w(s,u,v.body,r??void 0);j(!1),k.ok||A.error(`Update failed: ${k.error??"unknown"}`)};return t?e.jsx(q,{toolbar:e.jsxs(e.Fragment,{children:[e.jsx("span",{className:"text-fg-faint",children:"Agent"}),e.jsxs("select",{value:s??"",onChange:r=>f(r.target.value||null),className:"bg-bg border border-line rounded px-2 py-0.5 text-xs text-fg max-w-[16rem]",children:[o.length===0&&e.jsx("option",{value:"",children:"(no agents found)"}),o.map(r=>e.jsxs("option",{value:r.id,children:[r.label," ",r.scope==="project"?"· project":""]},`${r.scope}:${r.id}`))]}),e.jsx("span",{className:"mx-2 text-fg-faint",children:"·"}),e.jsxs("span",{className:"text-fg-faint",children:[E.length," ",E.length===1?"entry":"entries"]}),e.jsx("div",{className:"flex-1"}),e.jsx("button",{onClick:()=>c(!0),disabled:!s,className:"px-2 py-0.5 text-xs text-fg-dim hover:text-fg border border-line hover:border-fg-faint rounded transition-colors disabled:opacity-40",children:"+ New entry"}),e.jsx("button",{onClick:a,disabled:!s,className:"ml-2 px-2 py-0.5 text-xs text-fg-faint hover:text-fg-dim border border-line hover:border-fg-faint rounded transition-colors disabled:opacity-40",children:"Refresh"})]}),footer:v?e.jsxs("div",{className:"px-3 py-1.5 flex items-center gap-3 text-xs",children:[e.jsx("button",{onClick:p,disabled:!W||g,className:"px-2 py-0.5 text-xs text-fg-dim hover:text-fg border border-line hover:border-fg-faint rounded transition-colors disabled:opacity-40 disabled:cursor-not-allowed",children:g?"Saving…":"Save"}),e.jsx("button",{onClick:y,disabled:g,className:"px-2 py-0.5 text-xs text-red-400 hover:text-red-300 border border-line hover:border-red-400/50 rounded transition-colors disabled:opacity-40",children:"Delete"}),e.jsx("span",{className:"text-fg-faint",children:"Category:"}),e.jsxs("select",{value:v.category??"",onChange:r=>O(r.target.value||null),disabled:g,className:"bg-bg border border-line rounded px-2 py-0.5 text-xs text-fg-dim disabled:opacity-40",children:[e.jsx("option",{value:"",children:"(none)"}),ne.map(r=>e.jsx("option",{value:r,children:U[r]},r))]}),e.jsx("span",{className:"text-fg-faint truncate flex-1",children:v.id}),W&&e.jsx("span",{className:"text-accent",children:"unsaved"})]}):null,children:e.jsx(ee,{sidebar:e.jsxs("div",{className:"py-1",children:[e.jsx("div",{className:"px-2 pb-1",children:e.jsx("input",{value:b,onChange:r=>C(r.target.value),placeholder:"filter…",className:"w-full bg-bg border border-line rounded px-2 py-0.5 text-xs text-fg"})}),s?T.length===0?e.jsx("div",{className:"px-3 py-2 text-xs text-fg-faint italic",children:E.length===0?"no entries yet":"no matches"}):T.map(r=>e.jsxs("button",{onClick:()=>d(r.id),className:`w-full text-left px-3 py-1 text-xs flex items-center justify-between ${u===r.id?"bg-bg-hi text-fg":"text-fg-dim hover:text-fg hover:bg-bg-hi"}`,title:r.id,children:[e.jsx("span",{className:"truncate",children:r.id}),e.jsxs("span",{className:"ml-2 text-fg-faint shrink-0 text-[10px]",children:[H(r.bytes)," · ",V(r.updatedAt)]})]},r.id)):e.jsx("div",{className:"px-3 py-2 text-xs text-fg-faint italic",children:"select an agent first"})]}),detail:L&&s?e.jsx(pe,{agentId:s,existingIds:new Set(E.map(r=>r.id)),onCancel:()=>c(!1),onCreated:r=>{c(!1),d(r),R(s)}}):v&&s&&u&&S?e.jsxs("div",{className:"h-full flex flex-col",children:[e.jsxs("div",{className:"shrink-0 px-3 py-2 border-b border-line text-xs",children:[e.jsx("div",{className:"text-fg truncate",children:v.id}),e.jsxs("div",{className:"text-fg-faint truncate",children:[H(v.bytes)," · modified ",V(v.updatedAt),v.category?` · ${U[v.category]}`:""]})]}),e.jsx("div",{className:"flex-1 min-h-0",children:e.jsx(te,{path:`agent-memory/${s}/${v.id}.md`,value:P,onChange:r=>M(k=>({...k,[S]:r}))})})]}):e.jsx(F,{title:s?E.length===0?"no entries yet":"select an entry":"no agent selected",hint:s&&E.length===0?"Use “+ New entry” to give this subagent its first memory.":void 0})})}):e.jsx(F,{title:"loading…"})}function pe({agentId:t,existingIds:l,onCancel:n,onCreated:o}){const x=z(w=>w.setEntry),[s,f]=i.useState(""),[u,d]=i.useState(""),[b,C]=i.useState(""),[g,j]=i.useState(!1),[L,c]=i.useState(null),$=s.trim(),M=Y.test($)&&!l.has($),D=s?Y.test($)?l.has($)?"an entry with this id already exists":null:"must match [A-Za-z0-9._-]+":null,R=async()=>{if(!M)return;j(!0),c(null);const w=await x(t,$,u,b||void 0);if(j(!1),!w.ok){c(w.error??"create failed");return}o($)};return e.jsxs("div",{className:"p-4 max-w-xl space-y-3",children:[e.jsxs("div",{className:"text-sm text-fg",children:["New memory entry for ",e.jsx("span",{className:"font-mono text-fg-dim",children:t})]}),e.jsxs("div",{className:"space-y-1",children:[e.jsx("label",{className:"block text-xs text-fg-faint",children:"Entry id"}),e.jsx("input",{autoFocus:!0,value:s,onChange:w=>f(w.target.value),placeholder:"e.g. preferred-test-runner",className:"w-full bg-bg border border-line rounded px-2 py-1 text-xs text-fg font-mono"}),D&&e.jsx("div",{className:"text-[10px] text-red-400",children:D}),e.jsxs("div",{className:"text-[10px] text-fg-faint",children:["Letters, digits, dots, dashes, underscores. Persists at",e.jsxs("code",{className:"ml-1 font-mono text-fg-dim",children:["~/.claude/session-manager/agent-memory/",t,".json"]}),"."]})]}),e.jsxs("div",{className:"space-y-1",children:[e.jsx("label",{className:"block text-xs text-fg-faint",children:"Category (optional)"}),e.jsxs("select",{value:b,onChange:w=>C(w.target.value),className:"bg-bg border border-line rounded px-2 py-1 text-xs text-fg",children:[e.jsx("option",{value:"",children:"(none)"}),ne.map(w=>e.jsx("option",{value:w,children:U[w]},w))]})]}),e.jsxs("div",{className:"space-y-1",children:[e.jsx("label",{className:"block text-xs text-fg-faint",children:"Body (markdown)"}),e.jsx("textarea",{value:u,onChange:w=>d(w.target.value),rows:6,placeholder:"What should this subagent remember?",className:"w-full bg-bg border border-line rounded px-2 py-1 text-xs text-fg font-mono"})]}),L&&e.jsx("div",{className:"text-xs text-red-400",children:L}),e.jsxs("div",{className:"flex items-center gap-2",children:[e.jsx("button",{onClick:R,disabled:!M||g,className:"px-3 py-1 text-xs text-fg border border-line hover:bg-bg-hi rounded transition-colors disabled:opacity-40",children:g?"Creating…":"Create"}),e.jsx("button",{onClick:n,disabled:g,className:"px-3 py-1 text-xs text-fg-faint hover:text-fg-dim rounded transition-colors",children:"Cancel"})]})]})}function ge({workspace:t,cwd:l,onOpenMember:n}){const[o,x]=i.useState(null),[s,f]=i.useState(!1),[u,d]=i.useState(!1),b=i.useRef(0),C=i.useCallback(async c=>{const $=++b.current;f(!0);try{const M=await window.api.memory.aggregate(t,c,l??void 0);if(b.current!==$)return;x(M)}catch(M){if(b.current!==$)return;A.error(`Memory aggregate failed: ${M instanceof Error?M.message:String(M)}`)}finally{b.current===$&&(f(!1),d(!0))}},[t,l]);i.useEffect(()=>{x(null),d(!1),C(!1)},[C]);const g=(o==null?void 0:o.clusters)??[],j=(o==null?void 0:o.orphans)??[],L=g.length>0||j.length>0;return e.jsxs("div",{className:"h-full flex flex-col",children:[e.jsxs("div",{className:"shrink-0 flex items-center gap-2 px-3 py-2 border-b border-line text-xs",children:[e.jsx("span",{className:"text-fg-faint",children:o!=null&&o.generatedAt?`generated ${V(o.generatedAt)} ago`:"not yet aggregated"}),e.jsx("div",{className:"flex-1"}),e.jsx("button",{onClick:()=>C(!0),disabled:s,className:"px-2 py-0.5 text-xs text-fg-dim hover:text-fg border border-line hover:border-fg-faint rounded transition-colors disabled:opacity-40 disabled:cursor-not-allowed",children:s?"Aggregating…":o!=null&&o.generatedAt?"Refresh":"Aggregate"})]}),e.jsx("div",{className:"flex-1 min-h-0 overflow-auto p-3",children:u?L?e.jsxs("div",{className:"space-y-4",children:[g.length>0&&e.jsx("div",{className:"grid grid-cols-1 md:grid-cols-2 gap-3",children:g.map(c=>e.jsx(be,{cluster:c,onOpenMember:n},c.id))}),j.length>0&&e.jsxs("div",{className:"border border-line rounded p-3",children:[e.jsx("div",{className:"text-xs text-fg mb-2",children:"Unclustered"}),e.jsx("div",{className:"flex flex-wrap gap-1",children:j.map(c=>e.jsx("button",{onClick:()=>n(c),className:"px-2 py-0.5 text-[11px] font-mono text-fg-dim hover:text-fg border border-line hover:border-fg-faint rounded transition-colors",children:c},c))})]})]}):e.jsx(F,{title:"no clusters yet",hint:"Click Aggregate to organize this workspace's memories into named clusters."}):e.jsx(F,{title:"loading…"})})]})}function be({cluster:t,onOpenMember:l}){return e.jsxs("div",{className:"border border-line rounded p-3",children:[e.jsx("div",{className:"text-sm text-fg",children:t.name}),t.summary&&e.jsx("div",{className:"text-xs text-fg-faint mt-0.5",children:t.summary}),e.jsx("div",{className:"mt-2 flex flex-wrap gap-1",children:t.memberSlugs.map(n=>e.jsx("button",{onClick:()=>l(n),className:"px-2 py-0.5 text-[11px] font-mono text-fg-dim hover:text-fg border border-line hover:border-fg-faint rounded transition-colors",children:n},n))}),t.links.length>0&&e.jsx("div",{className:"mt-2 space-y-0.5",children:t.links.map((n,o)=>e.jsxs("div",{className:"text-[10px] text-fg-faint font-mono",children:[n.from," → ",n.to,n.label?` (${n.label})`:""]},`${n.from}-${n.to}-${o}`))})]})}const ye=[{key:"workspace",label:"Workspace"},{key:"subagent",label:"Subagent"}],J="sm.memoryTab.scope";function je(){const[t,l]=i.useState(()=>localStorage.getItem(J)==="subagent"?"subagent":"workspace");return i.useEffect(()=>{localStorage.setItem(J,t)},[t]),e.jsxs("div",{className:"h-full flex flex-col",children:[e.jsxs("div",{className:"shrink-0 flex items-center gap-3 px-3 py-2 border-b border-line",children:[e.jsx(se,{options:ye,active:t,onChange:l}),e.jsx("span",{className:"text-[11px] text-fg-faint",children:t==="workspace"?"memories scoped to the active project":"long-term memory per subagent"})]}),e.jsx("div",{className:"flex-1 min-h-0",children:t==="workspace"?e.jsx(ve,{}):e.jsx(he,{})})]})}const Me=i.memo(je);function ve(){const t=I(),l=K(),n=(l==null?void 0:l.cwd)??null,o=i.useMemo(()=>ie(n),[n]),[x,s]=i.useState("classic"),[f,u]=i.useState([]),[d,b]=i.useState(null),[C,g]=i.useState(""),[j,L]=i.useState({}),[c,$]=i.useState(!1),[M,D]=i.useState({}),[R,w]=i.useState({}),[_,E]=i.useState(!1),[T,v]=i.useState(!1),S=i.useCallback(async()=>{if(!t)return;const m=await window.api.memory.list(o);u(m.entries),m.error&&A.error(`Memory list failed: ${m.error}`),b(h=>{var N;return h&&m.entries.some(ae=>ae.name===h)?h:((N=m.entries[0])==null?void 0:N.name)??null});try{const h=await window.api.memory.stale(o,n??void 0);L(Object.fromEntries(h.entries.map(N=>[N.name,N])))}catch(h){L({}),A.error(`Memory staleness check failed: ${h instanceof Error?h.message:String(h)}`)}},[t,o,n]);if(i.useEffect(()=>{D({}),w({}),b(null),S()},[o,S]),i.useEffect(()=>{if(!t)return;const m=`${t}/.claude/projects/${o}/memory`;window.api.config.watch([m]);const h=window.api.config.onChanged(({path:N})=>{(N===m||N.startsWith(m+"/"))&&S()});return()=>{h(),window.api.config.unwatch([m])}},[t,o,S]),i.useEffect(()=>{if(!d||M[d]!==void 0)return;let m=!1;return(async()=>{const h=await window.api.memory.read(d,o);if(!m){if(h.error){A.error(`Memory read failed: ${h.error}`);return}D(N=>({...N,[d]:h.content})),w(N=>({...N,[d]:h.content}))}})(),()=>{m=!0}},[d,o]),!t)return e.jsx(F,{title:"loading…"});const P=f.reduce((m,h)=>{var N;return m+((N=j[h.name])!=null&&N.stale?1:0)},0),W=ce(f,C,c,j),a=f.find(m=>m.name===d)??null,p=d?j[d]:void 0,y=d?M[d]??"":"",O=d?R[d]??"":"",r=d?y!==O:!1,k=async()=>{if(!d)return;E(!0);const m=await window.api.memory.write(d,y,o);if(E(!1),!m.ok){A.error(`Memory save failed: ${m.error??"unknown error"}`);return}w(h=>({...h,[d]:y})),S()},B=async()=>{if(!d||!window.confirm(`Delete memory "${d}"? This cannot be undone.`))return;E(!0);const m=await window.api.memory.delete(d,o);if(E(!1),!m.ok){A.error(`Memory delete failed: ${m.error??"unknown error"}`);return}D(h=>{const N={...h};return delete N[d],N}),w(h=>{const N={...h};return delete N[d],N}),b(null),S()};return e.jsx(q,{toolbar:e.jsxs(e.Fragment,{children:[e.jsx(se,{options:[{key:"classic",label:"Editor"},{key:"natural",label:"Natural"},{key:"clusters",label:"Clusters"}],active:x,onChange:s}),e.jsx("span",{className:"mx-2 text-fg-faint",children:"·"}),e.jsx("span",{className:"text-fg-faint",children:"Workspace"}),e.jsx("span",{className:"font-mono text-fg-dim truncate max-w-[24rem]",title:o,children:o}),e.jsx("span",{className:"mx-2 text-fg-faint",children:"·"}),e.jsxs("span",{className:"text-fg-faint",children:[f.length," ",f.length===1?"entry":"entries"]}),e.jsx("div",{className:"flex-1"}),x==="classic"&&P>0&&e.jsxs("button",{onClick:()=>$(m=>!m),className:`px-2 py-0.5 text-xs border rounded transition-colors ${c?"text-amber-400 border-amber-400/50 bg-amber-400/10":"text-fg-dim hover:text-fg border-line hover:border-fg-faint"}`,children:["Stale only (",P,")"]}),x==="classic"&&e.jsx("button",{onClick:()=>v(!0),className:"px-2 py-0.5 text-xs text-fg-dim hover:text-fg border border-line hover:border-fg-faint rounded transition-colors",children:"+ New memory"}),x!=="clusters"&&e.jsx("button",{onClick:S,className:"ml-2 px-2 py-0.5 text-xs text-fg-faint hover:text-fg-dim border border-line hover:border-fg-faint rounded transition-colors",children:"Refresh"})]}),footer:x==="classic"&&a?e.jsxs("div",{className:"px-3 py-1.5 flex items-center gap-3 text-xs",children:[e.jsx("button",{onClick:k,disabled:!r||_,className:"px-2 py-0.5 text-xs text-fg-dim hover:text-fg border border-line hover:border-fg-faint rounded transition-colors disabled:opacity-40 disabled:cursor-not-allowed",children:_?"Saving…":"Save"}),e.jsx("button",{onClick:B,disabled:_,className:"px-2 py-0.5 text-xs text-red-400 hover:text-red-300 border border-line hover:border-red-400/50 rounded transition-colors disabled:opacity-40",children:"Delete"}),(p==null?void 0:p.stale)&&e.jsxs("span",{className:"text-amber-400/90 truncate",title:p.reasons.join("; "),children:["stale · ",p.reasons.join(", ")]}),e.jsx("span",{className:"text-fg-faint truncate flex-1",children:a.path}),r&&e.jsx("span",{className:"text-accent",children:"unsaved"})]}):null,children:x==="natural"?e.jsx(xe,{workspace:o,entries:f,onChanged:S}):x==="clusters"?e.jsx(ge,{workspace:o,cwd:n,onOpenMember:m=>{b(`${m}.md`),s("classic")}}):e.jsx(ee,{sidebar:e.jsxs("div",{className:"py-1",children:[e.jsx("div",{className:"px-2 pb-1",children:e.jsx("input",{value:C,onChange:m=>g(m.target.value),placeholder:"filter…",className:"w-full bg-bg border border-line rounded px-2 py-0.5 text-xs text-fg"})}),W.length===0?e.jsx("div",{className:"px-3 py-2 text-xs text-fg-faint italic",children:f.length===0?"no memories yet":"no matches"}):W.map(m=>{const h=j[m.name],N=h!=null&&h.stale?`${m.path}
|
|
8
|
-
stale: ${h.reasons.join(", ")}`:m.path;return e.jsxs("button",{onClick:()=>b(m.name),className:`w-full text-left px-3 py-1 text-xs flex items-center justify-between ${d===m.name?"bg-bg-hi text-fg":"text-fg-dim hover:text-fg hover:bg-bg-hi"}`,title:N,children:[e.jsxs("span",{className:"truncate flex items-center",children:[e.jsx("span",{className:"truncate",children:m.name.replace(/\.md$/,"")}),(h==null?void 0:h.stale)&&e.jsx("span",{className:"ml-1.5 shrink-0 px-1 rounded text-[9px] uppercase tracking-wide text-amber-400/90 border border-amber-400/30",children:"stale"})]}),e.jsxs("span",{className:"ml-2 text-fg-faint shrink-0 text-[10px]",children:[H(m.bytes)," · ",V(m.mtimeMs)]})]},m.name)})]}),detail:T?e.jsx(we,{workspace:o,onCancel:()=>v(!1),onCreated:m=>{v(!1),b(m),S()}}):a&&d?e.jsxs("div",{className:"h-full flex flex-col",children:[e.jsxs("div",{className:"shrink-0 px-3 py-2 border-b border-line text-xs",children:[e.jsx("div",{className:"text-fg truncate",children:a.name}),e.jsxs("div",{className:"text-fg-faint truncate",children:[H(a.bytes)," · modified ",V(a.mtimeMs)]})]}),e.jsx("div",{className:"flex-1 min-h-0",children:e.jsx(te,{path:a.path,value:y,onChange:m=>D(h=>({...h,[d]:m}))})})]}):e.jsx(F,{title:f.length===0?"no memories yet":"select a memory",hint:f.length===0?"Claude writes to memories during sessions when you ask it to remember things, or you can create one manually with the + New memory button.":void 0})})})}function we({workspace:t,onCancel:l,onCreated:n}){const[o,x]=i.useState(""),[s,f]=i.useState(""),[u,d]=i.useState(!1),[b,C]=i.useState(null),g=o.trim().toLowerCase(),j=re.test(g),L=async()=>{if(!j){C("Slug must match [a-z0-9-_]+");return}d(!0),C(null);const c=`${g}.md`,$=await window.api.memory.create(c,s.trim()||void 0,t);if(d(!1),!$.ok){C($.error??"create failed");return}n(c)};return e.jsxs("div",{className:"p-4 max-w-xl space-y-3",children:[e.jsx("div",{className:"text-sm text-fg",children:"New memory"}),e.jsxs("div",{className:"space-y-1",children:[e.jsx("label",{className:"block text-xs text-fg-faint",children:"Slug"}),e.jsxs("div",{className:"flex items-center gap-2",children:[e.jsx("input",{autoFocus:!0,value:o,onChange:c=>x(c.target.value),placeholder:"my-notes",className:"flex-1 bg-bg border border-line rounded px-2 py-1 text-xs text-fg font-mono"}),e.jsx("span",{className:"text-xs text-fg-faint",children:".md"})]}),e.jsxs("div",{className:"text-[10px] text-fg-faint",children:["Lowercase letters, digits, dashes, underscores. The file is created at",e.jsxs("code",{className:"ml-1 font-mono text-fg-dim",children:["~/.claude/projects/",t,"/memory/",g||"…",".md"]})]})]}),e.jsxs("div",{className:"space-y-1",children:[e.jsx("label",{className:"block text-xs text-fg-faint",children:"Description (optional)"}),e.jsx("input",{value:s,onChange:c=>f(c.target.value),placeholder:"one-line summary",className:"w-full bg-bg border border-line rounded px-2 py-1 text-xs text-fg"})]}),b&&e.jsx("div",{className:"text-xs text-red-400",children:b}),e.jsxs("div",{className:"flex items-center gap-2",children:[e.jsx("button",{onClick:L,disabled:!j||u,className:"px-3 py-1 text-xs text-fg border border-line hover:bg-bg-hi rounded transition-colors disabled:opacity-40",children:u?"Creating…":"Create"}),e.jsx("button",{onClick:l,disabled:u,className:"px-3 py-1 text-xs text-fg-faint hover:text-fg-dim rounded transition-colors",children:"Cancel"})]})]})}export{Me as Memory};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{a4 as P,b2 as G,aE as x,ao as i,Z as R,B as T,a as _}from"./index-CTBpf-26.js";import{a as E,C as B,c as L,d as S,b as I}from"./Library-DPE1h-1L.js";const O=e=>`${e}/.claude/session-manager/provenance.json`;function D(e){if(!e||typeof e!="object")return{};const t={};for(const[n,o]of Object.entries(e))(o==="anthropic"||o==="community"||o==="local")&&(t[n]=o);return t}const K=P((e,t)=>({overrides:{},loaded:!1,loading:!1,home:null,load:async n=>{const o=t();if(!(o.home===n&&(o.loaded||o.loading))){e({home:n,loading:!0});try{const r=await window.api.config.readJson(O(n)),d=r.exists?D(r.data):{};e(h=>({overrides:{...d,...h.overrides},loaded:!0,loading:!1}))}catch{e({loaded:!0,loading:!1})}}},setOverride:async(n,o)=>{const r={...t().overrides};o===null?delete r[n]:r[n]=o,e({overrides:r});const d=t().home;if(d)try{await window.api.config.writeJson(O(d),r)}catch{}}})),a=e=>(e??"").toLowerCase().trim(),j=new Set,$=new Set;for(const e of I){const t=e.official?j:$;t.add(a(e.id)),t.add(a(e.name))}const H=new Set(S.filter(e=>e.official).flatMap(e=>[a(e.id),a(e.name)])),J=new Set(S.filter(e=>!e.official).flatMap(e=>[a(e.id),a(e.name)])),U=new Set(L.filter(e=>e.official).flatMap(e=>[a(e.id),a(e.name)])),V=new Set(L.filter(e=>!e.official).flatMap(e=>[a(e.id),a(e.name)])),z=new Set(B.flatMap(e=>[a(e.id),a(e.name)])),Z=new Set(E.flatMap(e=>[a(e.id),a(e.name)])),q=new Set(E.map(e=>e.command.trim())),F=new Set(["security-review","pr-comments","release-notes","add-dir","code-review","simplify"]),Q=/(@anthropic-ai\/|anthropic\.com|github\.com\/anthropics)/i,W=/(^|\s)(npx|uvx|pipx|bunx|mcp-remote|deno\s+run|docker\s+run)(\s|$)/i;function X(e){return[e.command,...e.args??[],e.url,e.repository,e.homepage].filter(Boolean).join(" ").toLowerCase()}const l=e=>({provenance:"anthropic",reason:e}),y=e=>({provenance:"community",reason:e});function Y(e,t,n){switch(e){case"mcp":return j.has(t)?l("official MCP server (catalog)"):$.has(t)?y("third-party server (catalog)"):null;case"skill":case"command":return H.has(t)?l("official Anthropic skill (catalog)"):J.has(t)?y("third-party skill (catalog)"):null;case"plugin":return U.has(t)?l("official Anthropic plugin (catalog)"):V.has(t)?y("third-party plugin (catalog)"):null;case"subagent":return z.has(t)?l("bundled Anthropic subagent (catalog)"):null;case"hook":return Z.has(t)||n&&q.has(n.trim())?l("bundled hook (catalog)"):null}}function ee(e,t){return e.repository?"has a source repository":e.homepage?"has a homepage":e.url?"remote endpoint":W.test(t)?"installed from a package registry":null}function te(e){const t=a(e.name),n=X(e),o=Y(e.type,t,e.command);if(o)return o;if((e.type==="command"||e.type==="skill")&&F.has(t))return l("built-in Claude Code command");if(Q.test(n))return l("Anthropic package or repo");const r=ee(e,n);return r?y(r):{provenance:"local",reason:"no external source — assumed self-created"}}function ne(e,t,n){return`${e}:${t??"-"}:${n}`}const c={anthropic:"Anthropic",community:"Community",local:"Local"},N={anthropic:"accent",community:"warn",local:"dim"},oe=["anthropic","community","local"];function se({input:e,scope:t,interactive:n=!0,className:o=""}){const r=G(),{overrides:d,load:h,setOverride:k}=K(),[b,m]=x.useState(!1),v=x.useRef(null);x.useEffect(()=>{r&&h(r)},[r,h]),x.useEffect(()=>{if(!b)return;const s=C=>{v.current&&!v.current.contains(C.target)&&m(!1)},g=C=>{C.key==="Escape"&&m(!1)};return window.addEventListener("mousedown",s),window.addEventListener("keydown",g),()=>{window.removeEventListener("mousedown",s),window.removeEventListener("keydown",g)}},[b]);const w=ne(e.type,t??null,e.name),f=te(e),p=d[w],u=p??f.provenance,M=p?`Origin manually set to ${c[u]} — auto-detected ${c[f.provenance]} (${f.reason})`:`Auto-detected ${c[u]}: ${f.reason}`,A=i.jsxs(_,{tone:N[u],title:M,className:`${n?"cursor-pointer hover:brightness-125":""} ${o}`,children:[c[u],p&&i.jsx("span",{className:"w-1 h-1 rounded-full bg-current opacity-70","aria-hidden":!0})]});return n?i.jsxs("span",{ref:v,className:"relative inline-flex",children:[i.jsx("button",{type:"button",onClick:s=>{s.stopPropagation(),m(g=>!g)},className:"inline-flex","aria-label":`Origin: ${c[u]}. Click to change.`,children:A}),b&&i.jsxs("span",{className:`absolute ${R.dialog} top-full right-0 mt-1 min-w-[7.5rem] rounded-md border border-line bg-bg-elev shadow-lg py-1 text-left`,onClick:s=>s.stopPropagation(),children:[i.jsx("span",{className:"block px-2 pb-1 text-[10px] uppercase tracking-wide text-fg-faint",children:"Mark origin as"}),oe.map(s=>i.jsxs("button",{type:"button",onClick:()=>{k(w,s),m(!1)},className:`flex w-full items-center gap-2 px-2 py-1 text-[12px] hover:bg-bg-hi/60 ${u===s?"text-fg":"text-fg-dim"}`,children:[i.jsx("span",{className:`w-2 h-2 rounded-full border ${T[N[s]]}`,"aria-hidden":!0}),c[s],p===s&&i.jsx("span",{className:"ml-auto text-[10px] text-fg-faint",children:"set"})]},s)),i.jsx("div",{className:"my-1 border-t border-line"}),i.jsxs("button",{type:"button",onClick:()=>{k(w,null),m(!1)},disabled:!p,className:"flex w-full items-center gap-2 px-2 py-1 text-[12px] text-fg-dim hover:bg-bg-hi/60 disabled:opacity-40 disabled:hover:bg-transparent",title:`Revert to auto-detection (${c[f.provenance]})`,children:["Auto-detect",i.jsx("span",{className:"ml-auto text-[10px] text-fg-faint",children:c[f.provenance]})]})]})]}):A}export{se as P};
|