@lore-co/cli 0.2.4 → 0.2.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +10 -2
- package/dist/cli.d.ts +10 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +109 -27
- package/dist/cli.js.map +1 -1
- package/dist/command-help.d.ts +4 -1
- package/dist/command-help.d.ts.map +1 -1
- package/dist/command-help.js +43 -0
- package/dist/command-help.js.map +1 -1
- package/dist/config-command.d.ts +2 -0
- package/dist/config-command.d.ts.map +1 -0
- package/dist/config-command.js +46 -0
- package/dist/config-command.js.map +1 -0
- package/dist/connections.d.ts +28 -0
- package/dist/connections.d.ts.map +1 -0
- package/dist/connections.js +68 -0
- package/dist/connections.js.map +1 -0
- package/dist/devin-capture.d.ts +3 -1
- package/dist/devin-capture.d.ts.map +1 -1
- package/dist/devin-capture.js +17 -5
- package/dist/devin-capture.js.map +1 -1
- package/dist/devin-cli.d.ts.map +1 -1
- package/dist/devin-cli.js +12 -9
- package/dist/devin-cli.js.map +1 -1
- package/dist/directory-policy.d.ts +2 -0
- package/dist/directory-policy.d.ts.map +1 -0
- package/dist/directory-policy.js +2 -0
- package/dist/directory-policy.js.map +1 -0
- package/dist/generated-assets.d.ts +4 -4
- package/dist/generated-assets.d.ts.map +1 -1
- package/dist/generated-assets.js +4 -4
- package/dist/generated-assets.js.map +1 -1
- package/dist/guard.js +2 -2
- package/dist/guard.js.map +1 -1
- package/dist/host.d.ts +2 -1
- package/dist/host.d.ts.map +1 -1
- package/dist/host.js +17 -5
- package/dist/host.js.map +1 -1
- package/dist/image-capture.d.ts +8 -1
- package/dist/image-capture.d.ts.map +1 -1
- package/dist/image-capture.js +30 -19
- package/dist/image-capture.js.map +1 -1
- package/dist/invocation-health-writer.d.ts +1 -0
- package/dist/invocation-health-writer.d.ts.map +1 -1
- package/dist/invocation-health-writer.js +3 -0
- package/dist/invocation-health-writer.js.map +1 -1
- package/dist/live.d.ts.map +1 -1
- package/dist/live.js +45 -9
- package/dist/live.js.map +1 -1
- package/dist/recovery-cloud-receipt.d.ts.map +1 -1
- package/dist/recovery-cloud-receipt.js +36 -11
- package/dist/recovery-cloud-receipt.js.map +1 -1
- package/dist/recovery-cloud-workspace.d.ts.map +1 -1
- package/dist/recovery-cloud-workspace.js +4 -1
- package/dist/recovery-cloud-workspace.js.map +1 -1
- package/dist/recovery-cloud.d.ts +1 -0
- package/dist/recovery-cloud.d.ts.map +1 -1
- package/dist/recovery-cloud.js +45 -17
- package/dist/recovery-cloud.js.map +1 -1
- package/dist/recovery-codex.d.ts.map +1 -1
- package/dist/recovery-codex.js +2 -0
- package/dist/recovery-codex.js.map +1 -1
- package/dist/recovery-connection.d.ts +11 -0
- package/dist/recovery-connection.d.ts.map +1 -0
- package/dist/recovery-connection.js +35 -0
- package/dist/recovery-connection.js.map +1 -0
- package/dist/recovery-copilot.d.ts.map +1 -1
- package/dist/recovery-copilot.js +3 -0
- package/dist/recovery-copilot.js.map +1 -1
- package/dist/recovery-desktop.d.ts.map +1 -1
- package/dist/recovery-desktop.js +4 -0
- package/dist/recovery-desktop.js.map +1 -1
- package/dist/recovery-external.d.ts.map +1 -1
- package/dist/recovery-external.js +3 -0
- package/dist/recovery-external.js.map +1 -1
- package/dist/recovery-hosts.d.ts.map +1 -1
- package/dist/recovery-hosts.js +4 -6
- package/dist/recovery-hosts.js.map +1 -1
- package/dist/recovery-polytoken.d.ts.map +1 -1
- package/dist/recovery-polytoken.js +8 -8
- package/dist/recovery-polytoken.js.map +1 -1
- package/dist/recovery-remote.d.ts.map +1 -1
- package/dist/recovery-remote.js +22 -9
- package/dist/recovery-remote.js.map +1 -1
- package/dist/recovery-state.d.ts +2 -1
- package/dist/recovery-state.d.ts.map +1 -1
- package/dist/recovery-state.js +18 -3
- package/dist/recovery-state.js.map +1 -1
- package/dist/recovery-transfer.d.ts.map +1 -1
- package/dist/recovery-transfer.js +17 -9
- package/dist/recovery-transfer.js.map +1 -1
- package/dist/recovery.d.ts.map +1 -1
- package/dist/recovery.js +12 -18
- package/dist/recovery.js.map +1 -1
- package/dist/reliability-store.d.ts +5 -0
- package/dist/reliability-store.d.ts.map +1 -1
- package/dist/reliability-store.js +31 -3
- package/dist/reliability-store.js.map +1 -1
- package/dist/repository.d.ts +1 -3
- package/dist/repository.d.ts.map +1 -1
- package/dist/repository.js +1 -65
- package/dist/repository.js.map +1 -1
- package/dist/runtime.d.ts.map +1 -1
- package/dist/runtime.js +104 -39
- package/dist/runtime.js.map +1 -1
- package/dist/self-host.d.ts +1 -1
- package/dist/self-host.js +2 -2
- package/dist/stdin.d.ts +3 -0
- package/dist/stdin.d.ts.map +1 -0
- package/dist/stdin.js +21 -0
- package/dist/stdin.js.map +1 -0
- package/dist/update.js +3 -3
- package/package.json +7 -3
package/dist/runtime.js
CHANGED
|
@@ -6,12 +6,13 @@ import { dirname, parse, relative, resolve } from "node:path";
|
|
|
6
6
|
import { homedir } from "node:os";
|
|
7
7
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
8
8
|
import { promisify } from "node:util";
|
|
9
|
-
import { boundedUtf8Text, repositoryScopeFromGitRoot, } from "./repository.js";
|
|
9
|
+
import { boundedUtf8Text, repositoryScopeFromGitRoot, repositoryScopeFromDirectory, } from "./repository.js";
|
|
10
10
|
import { ReliabilityStore, ReliabilityStoreError, atomicWriteJson as durableAtomicWriteJson, classifyRetryFailure, createIntegrationInvocationAttempt, durableUnlink, recordLocalGuardMetric, recordLocalRetrievalMetric, } from "./reliability-store.js";
|
|
11
11
|
import { writeInvocationAttemptBounded, writeInvocationCompletionBounded, } from "./invocation-health-writer.js";
|
|
12
12
|
import { SignedSnapshotError, verifySignedSnapshot, } from "./signed-snapshot.js";
|
|
13
13
|
import { CONTEXT_CACHE_MAX_STALE_MS, isContextTransportFailure, selectCachedContext, } from "./context-fallback.js";
|
|
14
14
|
import { RUNTIME_VERSION } from "./runtime-version.js";
|
|
15
|
+
import { checkLoreAccess, checkDirectory, checkWorkspaceRepository, readLocalConfig, resolveLocalConnection, bindLocalSession } from "./directory-policy.js";
|
|
15
16
|
import { capturePendingImages, enqueueEventImages, flushImageAttachment, hostMessageImages, rememberImageCapture } from "./image-capture.js";
|
|
16
17
|
import { guardRuleConflict, guardToolTrigger, supportedGuardRule, guardEmergencyCommand, emergencyOverrideApplies } from "@lore-co/core/guard-evaluation";
|
|
17
18
|
const IS_STANDALONE_RUNTIME = typeof __LORE_STANDALONE__ === "boolean" && __LORE_STANDALONE__;
|
|
@@ -367,9 +368,11 @@ async function atomicWriteJson(path, value) {
|
|
|
367
368
|
await chmod(dirname(path), 0o700);
|
|
368
369
|
await durableAtomicWriteJson(path, value);
|
|
369
370
|
}
|
|
370
|
-
async function readRuntimeConfig(home) {
|
|
371
|
+
async function readRuntimeConfig(home, cwd) {
|
|
372
|
+
const saved = await readLocalConfig(home);
|
|
373
|
+
const selected = saved?.savedConnections && cwd !== undefined ? await resolveLocalConnection(cwd, home) : saved;
|
|
371
374
|
try {
|
|
372
|
-
const parsed =
|
|
375
|
+
const parsed = selected;
|
|
373
376
|
if (!isObject(parsed) ||
|
|
374
377
|
(parsed.version !== 1 && parsed.version !== 2) ||
|
|
375
378
|
typeof parsed.apiUrl !== "string" ||
|
|
@@ -394,6 +397,7 @@ async function readRuntimeConfig(home) {
|
|
|
394
397
|
: undefined;
|
|
395
398
|
return {
|
|
396
399
|
version: parsed.version,
|
|
400
|
+
...(typeof parsed.connectionId === "string" ? { connectionId: parsed.connectionId } : {}),
|
|
397
401
|
apiUrl: parsed.apiUrl,
|
|
398
402
|
...(dashboardUrl === undefined ? {} : { dashboardUrl }),
|
|
399
403
|
...(workspaceId === undefined ? {} : { workspaceId }),
|
|
@@ -406,17 +410,17 @@ async function readRuntimeConfig(home) {
|
|
|
406
410
|
return null;
|
|
407
411
|
}
|
|
408
412
|
}
|
|
409
|
-
function pendingPath(agent, sessionId, home) {
|
|
410
|
-
return resolve(loreDirectory(home), "state", "pending", `${sha256(`${agent}\0${sessionId}`)}.json`);
|
|
413
|
+
function pendingPath(agent, sessionId, home, connectionId) {
|
|
414
|
+
return resolve(loreDirectory(home), "state", "pending", `${sha256(`${connectionId ? `${connectionId}\0` : ""}${agent}\0${sessionId}`)}.json`);
|
|
411
415
|
}
|
|
412
|
-
async function savePending(input, agent, sessionId, now, home) {
|
|
416
|
+
async function savePending(input, agent, sessionId, now, home, connectionId) {
|
|
413
417
|
const assistantMessage = stringField(input.last_assistant_message);
|
|
414
418
|
if (assistantMessage === undefined) {
|
|
415
419
|
return;
|
|
416
420
|
}
|
|
417
421
|
const cwd = stringField(input.cwd);
|
|
418
422
|
const turnId = stringField(input.turn_id);
|
|
419
|
-
await atomicWriteJson(pendingPath(agent, sessionId, home), {
|
|
423
|
+
await atomicWriteJson(pendingPath(agent, sessionId, home, connectionId), {
|
|
420
424
|
agent,
|
|
421
425
|
sessionId,
|
|
422
426
|
assistantMessage: redactSecrets(assistantMessage),
|
|
@@ -425,8 +429,8 @@ async function savePending(input, agent, sessionId, now, home) {
|
|
|
425
429
|
capturedAt: now.toISOString(),
|
|
426
430
|
});
|
|
427
431
|
}
|
|
428
|
-
async function readPending(agent, sessionId, home) {
|
|
429
|
-
const path = pendingPath(agent, sessionId, home);
|
|
432
|
+
async function readPending(agent, sessionId, home, connectionId) {
|
|
433
|
+
const path = pendingPath(agent, sessionId, home, connectionId);
|
|
430
434
|
try {
|
|
431
435
|
const parsed = JSON.parse(await readFile(path, "utf8"));
|
|
432
436
|
if (!isObject(parsed) ||
|
|
@@ -456,8 +460,8 @@ async function readPending(agent, sessionId, home) {
|
|
|
456
460
|
return null;
|
|
457
461
|
}
|
|
458
462
|
}
|
|
459
|
-
async function clearPending(agent, sessionId, home) {
|
|
460
|
-
await durableUnlink(pendingPath(agent, sessionId, home), true);
|
|
463
|
+
async function clearPending(agent, sessionId, home, connectionId) {
|
|
464
|
+
await durableUnlink(pendingPath(agent, sessionId, home, connectionId), true);
|
|
461
465
|
}
|
|
462
466
|
function normalizedRepositoryPath(value) {
|
|
463
467
|
const parts = [];
|
|
@@ -983,10 +987,12 @@ function runtimeAccept(config) {
|
|
|
983
987
|
async function runtimeStore(config, home) {
|
|
984
988
|
const store = new ReliabilityStore(reliabilityWorkspaceKey(config), {
|
|
985
989
|
home: homeDirectory(home),
|
|
990
|
+
...(config.connectionId ? { connectionId: config.connectionId } : {}),
|
|
986
991
|
});
|
|
987
992
|
await store.initialize();
|
|
988
|
-
|
|
989
|
-
|
|
993
|
+
if (!config.connectionId)
|
|
994
|
+
await store.migrateLegacyQueue(queueDirectory(home));
|
|
995
|
+
if (config.workspaceId !== undefined && !config.connectionId) {
|
|
990
996
|
const credentialStore = new ReliabilityStore(credentialReliabilityWorkspaceKey(config), { home: homeDirectory(home) });
|
|
991
997
|
await credentialStore.transferPendingTo(store).catch(() => undefined);
|
|
992
998
|
}
|
|
@@ -1511,6 +1517,7 @@ async function flushOne(config, fetchImplementation, store) {
|
|
|
1511
1517
|
const claimed = await store.claimNext({
|
|
1512
1518
|
workerId: `native-hook:${process.pid}`,
|
|
1513
1519
|
kinds: ["turn", "prompt", "guard_override"],
|
|
1520
|
+
eligible: async (entry) => entry.kind === "guard_override" || (await checkWorkspaceRepository(config, queuedRequest(entry.payload)?.request.scope?.repo, fetchImplementation)).allowed,
|
|
1514
1521
|
});
|
|
1515
1522
|
if (claimed === null || claimed.claim === undefined) {
|
|
1516
1523
|
return null;
|
|
@@ -1541,6 +1548,9 @@ async function flushOne(config, fetchImplementation, store) {
|
|
|
1541
1548
|
state: failed.state === "auth-blocked" ? "auth-blocked" : "dead",
|
|
1542
1549
|
};
|
|
1543
1550
|
}
|
|
1551
|
+
const permission = await checkWorkspaceRepository(config, queued.request.scope?.repo, fetchImplementation);
|
|
1552
|
+
if (!permission.allowed)
|
|
1553
|
+
throw Object.assign(new Error(permission.reason), { retryable: true });
|
|
1544
1554
|
let delivery;
|
|
1545
1555
|
if (queued.kind === "turn") {
|
|
1546
1556
|
delivery = await postTurn(config, queued.request, fetchImplementation, store);
|
|
@@ -1631,12 +1641,12 @@ const GUARD_SHELL_TOOLS = new Set(["Bash", "bash", "Shell", "shell", "exec_comma
|
|
|
1631
1641
|
export function guardTrigger(toolName, toolInput) {
|
|
1632
1642
|
return guardToolTrigger(toolName, toolInput);
|
|
1633
1643
|
}
|
|
1634
|
-
function guardStatePath(agent, sessionId, home) {
|
|
1635
|
-
return resolve(loreDirectory(home), "state", "guard", `${sha256(`${agent}\0${sessionId}`)}.json`);
|
|
1644
|
+
function guardStatePath(agent, sessionId, home, connectionId) {
|
|
1645
|
+
return resolve(loreDirectory(home), "state", "guard", `${sha256(`${connectionId ? `${connectionId}\0` : ""}${agent}\0${sessionId}`)}.json`);
|
|
1636
1646
|
}
|
|
1637
|
-
async function readGuardState(agent, sessionId, home) {
|
|
1647
|
+
async function readGuardState(agent, sessionId, home, connectionId) {
|
|
1638
1648
|
try {
|
|
1639
|
-
const parsed = JSON.parse(await readFile(guardStatePath(agent, sessionId, home), "utf8"));
|
|
1649
|
+
const parsed = JSON.parse(await readFile(guardStatePath(agent, sessionId, home, connectionId), "utf8"));
|
|
1640
1650
|
if (isObject(parsed)) {
|
|
1641
1651
|
return {
|
|
1642
1652
|
...(typeof parsed.mode === "string" ? { mode: parsed.mode } : {}),
|
|
@@ -1654,12 +1664,12 @@ async function readGuardState(agent, sessionId, home) {
|
|
|
1654
1664
|
}
|
|
1655
1665
|
return { keys: {} };
|
|
1656
1666
|
}
|
|
1657
|
-
async function writeGuardState(agent, sessionId, state, home) {
|
|
1667
|
+
async function writeGuardState(agent, sessionId, state, home, connectionId) {
|
|
1658
1668
|
const entries = Object.entries(state.keys)
|
|
1659
1669
|
.sort((a, b) => (a[1] < b[1] ? 1 : -1))
|
|
1660
1670
|
.slice(0, GUARD_MAX_CACHED_KEYS);
|
|
1661
1671
|
try {
|
|
1662
|
-
await atomicWriteJson(guardStatePath(agent, sessionId, home), {
|
|
1672
|
+
await atomicWriteJson(guardStatePath(agent, sessionId, home, connectionId), {
|
|
1663
1673
|
...state,
|
|
1664
1674
|
keys: Object.fromEntries(entries),
|
|
1665
1675
|
});
|
|
@@ -1890,7 +1900,7 @@ async function handleGuardEvent(input, agent, config, sessionId, options, now) {
|
|
|
1890
1900
|
return undefined;
|
|
1891
1901
|
}
|
|
1892
1902
|
const store = await runtimeStore(config, options.home);
|
|
1893
|
-
const state = await readGuardState(agent, sessionId, options.home);
|
|
1903
|
+
const state = await readGuardState(agent, sessionId, options.home, config.connectionId);
|
|
1894
1904
|
const rememberedPolicy = await store.readState("guard-policy-state");
|
|
1895
1905
|
const enforcementMayHaveChanged = isObject(rememberedPolicy) && (rememberedPolicy.mode === "enforce" ||
|
|
1896
1906
|
(Array.isArray(rememberedPolicy.overrides) && rememberedPolicy.overrides.some((entry) => isObject(entry) && entry.mode === "enforce")));
|
|
@@ -1971,7 +1981,7 @@ async function handleGuardEvent(input, agent, config, sessionId, options, now) {
|
|
|
1971
1981
|
if (!result.requiresConfirmation && !result.policyUnavailable) {
|
|
1972
1982
|
state.keys[key] = now.toISOString();
|
|
1973
1983
|
}
|
|
1974
|
-
await writeGuardState(agent, sessionId, state, options.home).catch(() => undefined);
|
|
1984
|
+
await writeGuardState(agent, sessionId, state, options.home, config.connectionId).catch(() => undefined);
|
|
1975
1985
|
if (result.mode === "off") {
|
|
1976
1986
|
return undefined;
|
|
1977
1987
|
}
|
|
@@ -2040,10 +2050,45 @@ export async function handleHookEvent(value, agent, options = {}) {
|
|
|
2040
2050
|
!validHookInput(input, agent, eventName)) {
|
|
2041
2051
|
throw new InvalidHookInputError();
|
|
2042
2052
|
}
|
|
2043
|
-
|
|
2053
|
+
let config;
|
|
2054
|
+
try {
|
|
2055
|
+
config = await readRuntimeConfig(options.home, stringField(input.cwd) ?? "");
|
|
2056
|
+
}
|
|
2057
|
+
catch (error) {
|
|
2058
|
+
if (eventName !== "UserPromptSubmit" && eventName !== "SessionStart")
|
|
2059
|
+
return undefined;
|
|
2060
|
+
const notice = `Lore is off: ${error instanceof Error ? error.message : "connection unavailable"}`;
|
|
2061
|
+
return agent === "cursor" ? { continue: true, userMessage: notice } : agent === "polytoken" ? { outcome: "accept", additional_context: notice } : { systemMessage: notice };
|
|
2062
|
+
}
|
|
2044
2063
|
if (config === null || !config.agents.includes(agent)) {
|
|
2045
2064
|
return undefined;
|
|
2046
2065
|
}
|
|
2066
|
+
if (!await bindLocalSession(homeDirectory(options.home), agent, sessionId, config.connectionId)) {
|
|
2067
|
+
const notice = "Lore is paused for this conversation because its account or workspace changed. Start a new agent session.";
|
|
2068
|
+
return eventName === "UserPromptSubmit" ? (agent === "cursor" ? { continue: true, userMessage: notice } : { systemMessage: notice }) : undefined;
|
|
2069
|
+
}
|
|
2070
|
+
const access = await checkLoreAccess(stringField(input.cwd), options.home, config, options.fetch ?? globalThis.fetch);
|
|
2071
|
+
if (!access.allowed) {
|
|
2072
|
+
await clearPending(agent, sessionId, options.home, config.connectionId);
|
|
2073
|
+
// A policy outage must not silently bypass an already enforced Guard.
|
|
2074
|
+
if (eventName === "PreToolUse" && access.reason === "Workspace repository settings are unavailable") {
|
|
2075
|
+
const prior = await readGuardState(agent, sessionId, options.home, config.connectionId).catch(() => null);
|
|
2076
|
+
const saved = await runtimeStore(config, options.home).then(store => store.readState("guard-policy-state")).catch(() => null);
|
|
2077
|
+
const repo = stringField(input.cwd) ? await repositoryScopeFromDirectory(stringField(input.cwd)) : undefined;
|
|
2078
|
+
const override = isObject(saved) && Array.isArray(saved.overrides) ? saved.overrides.find(value => isObject(value) && value.repo === repo) : undefined;
|
|
2079
|
+
if (prior?.mode === "enforce" || (isObject(override) ? override.mode : isObject(saved) ? saved.mode : undefined) === "enforce") {
|
|
2080
|
+
const reason = "Lore cannot verify workspace repository settings. Restore the connection before retrying.";
|
|
2081
|
+
if (agent === "cursor")
|
|
2082
|
+
return { permission: "deny", userMessage: reason, agentMessage: reason };
|
|
2083
|
+
if (agent === "copilot-cli")
|
|
2084
|
+
return { permissionDecision: "deny", permissionDecisionReason: reason };
|
|
2085
|
+
if (agent === "polytoken")
|
|
2086
|
+
return { outcome: "deny", reason };
|
|
2087
|
+
return { hookSpecificOutput: { hookEventName: "PreToolUse", permissionDecision: "deny", permissionDecisionReason: reason } };
|
|
2088
|
+
}
|
|
2089
|
+
}
|
|
2090
|
+
return agent === "cursor" && eventName === "UserPromptSubmit" ? { continue: true } : undefined;
|
|
2091
|
+
}
|
|
2047
2092
|
const now = (options.now ?? (() => new Date()))();
|
|
2048
2093
|
if (agent === "cursor" && eventName === "SessionStart") {
|
|
2049
2094
|
const cwd = stringField(input.cwd);
|
|
@@ -2060,7 +2105,7 @@ export async function handleHookEvent(value, agent, options = {}) {
|
|
|
2060
2105
|
].join("\n") };
|
|
2061
2106
|
}
|
|
2062
2107
|
if (eventName === "Stop" || eventName === "AssistantResponse") {
|
|
2063
|
-
await savePending(input, agent, sessionId, now, options.home);
|
|
2108
|
+
await savePending(input, agent, sessionId, now, options.home, config.connectionId);
|
|
2064
2109
|
{
|
|
2065
2110
|
const store = await runtimeStore(config, options.home);
|
|
2066
2111
|
const codexHome = (options.environment ?? process.env).CODEX_HOME ?? resolve(homeDirectory(options.home), ".codex");
|
|
@@ -2069,7 +2114,7 @@ export async function handleHookEvent(value, agent, options = {}) {
|
|
|
2069
2114
|
const target = await store.readState(`image-event-${agent}-${sha256(sessionId)}`);
|
|
2070
2115
|
if (isObject(target) && typeof target.eventId === "string") {
|
|
2071
2116
|
await hostMessageImages(input.attachments ?? input.parts ?? input.content, "assistant")
|
|
2072
|
-
.then(images => enqueueEventImages(store, { connector: "lore-cli", eventId: String(target.eventId), sessionId }, images)).catch(() => undefined);
|
|
2117
|
+
.then(images => enqueueEventImages(store, { connector: "lore-cli", eventId: String(target.eventId), sessionId, cwd: stringField(target.cwd) }, images)).catch(() => undefined);
|
|
2073
2118
|
}
|
|
2074
2119
|
await flushImageAttachment(store, config, options.fetch ?? globalThis.fetch).catch(() => undefined);
|
|
2075
2120
|
}
|
|
@@ -2086,7 +2131,7 @@ export async function handleHookEvent(value, agent, options = {}) {
|
|
|
2086
2131
|
return await handleGuardEvent(input, agent, config, sessionId, options, now);
|
|
2087
2132
|
}
|
|
2088
2133
|
catch {
|
|
2089
|
-
const prior = await readGuardState(agent, sessionId, options.home).catch(() => null);
|
|
2134
|
+
const prior = await readGuardState(agent, sessionId, options.home, config.connectionId).catch(() => null);
|
|
2090
2135
|
const saved = await runtimeStore(config, options.home).then((store) => store.readState("guard-policy-state")).catch(() => null);
|
|
2091
2136
|
const scope = await repositoryScope(stringField(input.cwd)).catch(() => undefined);
|
|
2092
2137
|
const override = isObject(saved) && Array.isArray(saved.overrides) ? saved.overrides.find((entry) => isObject(entry) && entry.repo === scope?.repo) : undefined;
|
|
@@ -2142,7 +2187,16 @@ function cursorSessionKey(sessionId) {
|
|
|
2142
2187
|
}
|
|
2143
2188
|
/** MCP requests are bound to a session/workspace observed by the native hook. */
|
|
2144
2189
|
async function cursorSession(sessionId, options) {
|
|
2145
|
-
const
|
|
2190
|
+
const bindingPath = resolve(loreDirectory(options.home), "state", "connection-sessions", `${sha256(`cursor\0${sessionId}`)}.json`);
|
|
2191
|
+
const binding = await readFile(bindingPath, "utf8").then(value => JSON.parse(value)).catch(() => undefined);
|
|
2192
|
+
let config = await readRuntimeConfig(options.home);
|
|
2193
|
+
if (binding?.connectionId) {
|
|
2194
|
+
const connections = await import("./directory-policy.js").then(module => module.savedLocalConnections(options.home));
|
|
2195
|
+
const saved = connections.find(value => value.connectionId === binding.connectionId);
|
|
2196
|
+
if (!saved)
|
|
2197
|
+
throw new Error("This Cursor connection is no longer available. Start a new conversation.");
|
|
2198
|
+
config = { ...saved, version: 2, agents: Array.isArray(saved.agents) ? saved.agents.filter(isCommandHookAgent) : [] };
|
|
2199
|
+
}
|
|
2146
2200
|
if (!config?.agents.includes("cursor"))
|
|
2147
2201
|
throw new Error("Reconnect Cursor to Lore before requesting context.");
|
|
2148
2202
|
const store = await runtimeStore(config, options.home);
|
|
@@ -2150,6 +2204,9 @@ async function cursorSession(sessionId, options) {
|
|
|
2150
2204
|
if (!isObject(state) || state.sessionId !== sessionId || !stringField(state.cwd)) {
|
|
2151
2205
|
throw new Error("Start a new Cursor conversation to register this Lore session.");
|
|
2152
2206
|
}
|
|
2207
|
+
const access = await checkLoreAccess(stringField(state.cwd), options.home, config, options.fetch ?? globalThis.fetch);
|
|
2208
|
+
if (!access.allowed)
|
|
2209
|
+
throw new Error(`Lore is off: ${access.reason}.`);
|
|
2153
2210
|
return { config, store, state, cwd: stringField(state.cwd) };
|
|
2154
2211
|
}
|
|
2155
2212
|
export async function getCursorContext(sessionId, prompt, options = {}) {
|
|
@@ -2162,9 +2219,9 @@ export async function getCursorContext(sessionId, prompt, options = {}) {
|
|
|
2162
2219
|
return result?.additional_context || "No relevant shared knowledge was found for this request.";
|
|
2163
2220
|
}
|
|
2164
2221
|
export async function completeCursorResponse(sessionId, response, options = {}) {
|
|
2165
|
-
const { store, state, cwd } = await cursorSession(sessionId, options);
|
|
2222
|
+
const { config, store, state, cwd } = await cursorSession(sessionId, options);
|
|
2166
2223
|
const now = (options.now ?? (() => new Date()))();
|
|
2167
|
-
await savePending({ cwd, last_assistant_message: response }, "cursor", sessionId, now, options.home);
|
|
2224
|
+
await savePending({ cwd, last_assistant_message: response }, "cursor", sessionId, now, options.home, config.connectionId);
|
|
2168
2225
|
const { prompt: _prompt, promptId: _promptId, ...rest } = state;
|
|
2169
2226
|
await store.writeState(cursorSessionKey(sessionId), { ...rest, updatedAt: now.toISOString() });
|
|
2170
2227
|
}
|
|
@@ -2210,18 +2267,23 @@ async function handlePromptEvent(input, agent, sessionId, config, options, now,
|
|
|
2210
2267
|
catch {
|
|
2211
2268
|
notices.push("Lore retained queued captures locally, but replay could not run. Run lore doctor.");
|
|
2212
2269
|
}
|
|
2213
|
-
|
|
2270
|
+
let pending = capture ? await readPending(agent, sessionId, options.home, config.connectionId) : null;
|
|
2271
|
+
if (pending && (!(await checkDirectory(pending.cwd, options.home)).allowed ||
|
|
2272
|
+
(pending.cwd !== undefined && pending.cwd !== stringField(input.cwd)))) {
|
|
2273
|
+
await clearPending(agent, sessionId, options.home, config.connectionId);
|
|
2274
|
+
pending = null;
|
|
2275
|
+
}
|
|
2214
2276
|
const codexHome = (options.environment ?? process.env).CODEX_HOME ?? resolve(homeDirectory(options.home), ".codex");
|
|
2215
2277
|
const claudeHome = (options.environment ?? process.env).CLAUDE_CONFIG_DIR ?? resolve(homeDirectory(options.home), ".claude");
|
|
2216
2278
|
await capturePendingImages(store, sessionId, codexHome, claudeHome).catch(() => undefined);
|
|
2217
2279
|
const rememberImages = async (eventId) => {
|
|
2218
2280
|
if (!capture)
|
|
2219
2281
|
return;
|
|
2220
|
-
await store.writeState(`image-event-${agent}-${sha256(sessionId)}`, { eventId });
|
|
2221
|
-
await enqueueEventImages(store, { connector: "lore-cli", eventId, sessionId }, await hostMessageImages(input.attachments ?? input.parts ?? input.content));
|
|
2282
|
+
await store.writeState(`image-event-${agent}-${sha256(sessionId)}`, { eventId, ...(stringField(input.cwd) ? { cwd: stringField(input.cwd) } : {}) });
|
|
2283
|
+
await enqueueEventImages(store, { connector: "lore-cli", eventId, sessionId, cwd: stringField(input.cwd) }, await hostMessageImages(input.attachments ?? input.parts ?? input.content));
|
|
2222
2284
|
if (agent !== "codex" && agent !== "claude")
|
|
2223
2285
|
return;
|
|
2224
|
-
await rememberImageCapture(store, { eventId, sessionId, prompt, occurredAt: now.toISOString(), agent,
|
|
2286
|
+
await rememberImageCapture(store, { eventId, sessionId, prompt, occurredAt: now.toISOString(), agent, cwd: stringField(input.cwd),
|
|
2225
2287
|
...(typeof input.transcript_path === "string" ? { transcriptPath: input.transcript_path } : {}),
|
|
2226
2288
|
});
|
|
2227
2289
|
await capturePendingImages(store, sessionId, codexHome, claudeHome);
|
|
@@ -2239,7 +2301,7 @@ async function handlePromptEvent(input, agent, sessionId, config, options, now,
|
|
|
2239
2301
|
await enqueueCapture(store, { kind: "turn", request });
|
|
2240
2302
|
enqueued = true;
|
|
2241
2303
|
await rememberImages(request.eventId).catch(() => undefined);
|
|
2242
|
-
await clearPending(agent, sessionId, options.home);
|
|
2304
|
+
await clearPending(agent, sessionId, options.home, config.connectionId);
|
|
2243
2305
|
}
|
|
2244
2306
|
catch (error) {
|
|
2245
2307
|
notices.push(error instanceof ReliabilityStoreError &&
|
|
@@ -2440,22 +2502,22 @@ function nativeIntegrationId(agent) {
|
|
|
2440
2502
|
? "preview/copilot-vscode"
|
|
2441
2503
|
: `native/${agent}`;
|
|
2442
2504
|
}
|
|
2443
|
-
async function beginHookInvocation(agent) {
|
|
2444
|
-
const config = await readRuntimeConfig();
|
|
2505
|
+
async function beginHookInvocation(agent, cwd) {
|
|
2506
|
+
const config = await readRuntimeConfig(undefined, cwd);
|
|
2445
2507
|
if (config === null || !config.agents.includes(agent)) {
|
|
2446
2508
|
return null;
|
|
2447
2509
|
}
|
|
2448
2510
|
const workspaceKey = reliabilityWorkspaceKey(config);
|
|
2449
2511
|
const home = homeDirectory();
|
|
2450
2512
|
const attempt = createIntegrationInvocationAttempt(nativeIntegrationId(agent), { runtimeVersion: RUNTIME_VERSION });
|
|
2451
|
-
const recorded = await writeInvocationAttemptBounded(workspaceKey, attempt, { home, standalone: IS_STANDALONE_RUNTIME });
|
|
2452
|
-
return recorded ? { workspaceKey, home, attempt } : null;
|
|
2513
|
+
const recorded = await writeInvocationAttemptBounded(workspaceKey, attempt, { home, standalone: IS_STANDALONE_RUNTIME, ...(config.connectionId ? { connectionId: config.connectionId } : {}) });
|
|
2514
|
+
return recorded ? { workspaceKey, home, attempt, ...(config.connectionId ? { connectionId: config.connectionId } : {}) } : null;
|
|
2453
2515
|
}
|
|
2454
2516
|
async function finishHookInvocation(tracker, outcome) {
|
|
2455
2517
|
if (tracker === null) {
|
|
2456
2518
|
return;
|
|
2457
2519
|
}
|
|
2458
|
-
await writeInvocationCompletionBounded(tracker.workspaceKey, tracker.attempt, outcome, { home: tracker.home, standalone: IS_STANDALONE_RUNTIME }).catch(() => false);
|
|
2520
|
+
await writeInvocationCompletionBounded(tracker.workspaceKey, tracker.attempt, outcome, { home: tracker.home, standalone: IS_STANDALONE_RUNTIME, ...(tracker.connectionId ? { connectionId: tracker.connectionId } : {}) }).catch(() => false);
|
|
2459
2521
|
}
|
|
2460
2522
|
export async function runHook(args = process.argv.slice(2)) {
|
|
2461
2523
|
const agent = parseAgent(args);
|
|
@@ -2465,12 +2527,15 @@ export async function runHook(args = process.argv.slice(2)) {
|
|
|
2465
2527
|
if (agent === null) {
|
|
2466
2528
|
return;
|
|
2467
2529
|
}
|
|
2468
|
-
|
|
2530
|
+
let tracker = null;
|
|
2469
2531
|
let input;
|
|
2470
2532
|
try {
|
|
2471
2533
|
input = await readStdin();
|
|
2534
|
+
const normalized = isObject(input) ? normalizeHookInput(input, agent, process.env).input : {};
|
|
2535
|
+
tracker = await beginHookInvocation(agent, stringField(normalized.cwd) ?? "").catch(() => null);
|
|
2472
2536
|
}
|
|
2473
2537
|
catch {
|
|
2538
|
+
tracker = await beginHookInvocation(agent).catch(() => null);
|
|
2474
2539
|
await finishHookInvocation(tracker, {
|
|
2475
2540
|
success: false,
|
|
2476
2541
|
failureCode: "invalid_input",
|