@lore-co/cli 0.2.26 → 0.2.27
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 +62 -16
- package/dist/agents.d.ts +9 -0
- package/dist/agents.d.ts.map +1 -1
- package/dist/agents.js +19 -6
- package/dist/agents.js.map +1 -1
- package/dist/api.d.ts +2 -1
- package/dist/api.d.ts.map +1 -1
- package/dist/api.js +15 -2
- package/dist/api.js.map +1 -1
- package/dist/approach-guidance.d.ts +8 -1
- package/dist/approach-guidance.d.ts.map +1 -1
- package/dist/approach-guidance.js +13 -3
- package/dist/approach-guidance.js.map +1 -1
- package/dist/approach.d.ts.map +1 -1
- package/dist/approach.js +2 -2
- package/dist/approach.js.map +1 -1
- package/dist/ask.d.ts.map +1 -1
- package/dist/ask.js +2 -2
- package/dist/ask.js.map +1 -1
- package/dist/capture-scope.d.ts.map +1 -1
- package/dist/capture-scope.js +2 -1
- package/dist/capture-scope.js.map +1 -1
- package/dist/chat.d.ts.map +1 -1
- package/dist/chat.js +2 -2
- package/dist/chat.js.map +1 -1
- package/dist/cli.d.ts +60 -2
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +512 -93
- package/dist/cli.js.map +1 -1
- package/dist/codex-hook-approval.d.ts.map +1 -1
- package/dist/codex-hook-approval.js +9 -2
- package/dist/codex-hook-approval.js.map +1 -1
- package/dist/command-help.d.ts.map +1 -1
- package/dist/command-help.js +7 -3
- package/dist/command-help.js.map +1 -1
- package/dist/config-command.js +2 -2
- package/dist/config-command.js.map +1 -1
- package/dist/connect-project.d.ts.map +1 -1
- package/dist/connect-project.js +2 -1
- package/dist/connect-project.js.map +1 -1
- package/dist/console-keys.d.ts +11 -0
- package/dist/console-keys.d.ts.map +1 -0
- package/dist/console-keys.js +24 -0
- package/dist/console-keys.js.map +1 -0
- package/dist/cursor-mcp.d.ts.map +1 -1
- package/dist/cursor-mcp.js +4 -0
- package/dist/cursor-mcp.js.map +1 -1
- package/dist/demo-process.js +3 -3
- package/dist/demo-process.js.map +1 -1
- package/dist/devin-capture.d.ts.map +1 -1
- package/dist/devin-capture.js +2 -2
- package/dist/devin-capture.js.map +1 -1
- package/dist/devin-cli.d.ts.map +1 -1
- package/dist/devin-cli.js +23 -13
- package/dist/devin-cli.js.map +1 -1
- package/dist/devin-client.d.ts +3 -0
- package/dist/devin-client.d.ts.map +1 -1
- package/dist/devin-client.js +5 -0
- package/dist/devin-client.js.map +1 -1
- package/dist/devin.d.ts.map +1 -1
- package/dist/devin.js +29 -9
- package/dist/devin.js.map +1 -1
- package/dist/generated-assets.d.ts +8 -8
- package/dist/generated-assets.d.ts.map +1 -1
- package/dist/generated-assets.js +8 -8
- package/dist/generated-assets.js.map +1 -1
- package/dist/github.d.ts.map +1 -1
- package/dist/github.js +38 -7
- package/dist/github.js.map +1 -1
- package/dist/guard.d.ts +1 -1
- package/dist/guard.d.ts.map +1 -1
- package/dist/guard.js +42 -14
- package/dist/guard.js.map +1 -1
- package/dist/handoff.js +2 -2
- package/dist/handoff.js.map +1 -1
- package/dist/host.js +4 -4
- package/dist/host.js.map +1 -1
- package/dist/image-capture.d.ts.map +1 -1
- package/dist/image-capture.js +5 -4
- package/dist/image-capture.js.map +1 -1
- package/dist/learnings.d.ts.map +1 -1
- package/dist/learnings.js +3 -3
- package/dist/learnings.js.map +1 -1
- package/dist/live.d.ts +7 -0
- package/dist/live.d.ts.map +1 -1
- package/dist/live.js +11 -6
- package/dist/live.js.map +1 -1
- package/dist/lore-agent.d.ts +6 -1
- package/dist/lore-agent.d.ts.map +1 -1
- package/dist/lore-agent.js +26 -7
- package/dist/lore-agent.js.map +1 -1
- package/dist/mcp-config.d.ts +7 -0
- package/dist/mcp-config.d.ts.map +1 -1
- package/dist/mcp-config.js +19 -3
- package/dist/mcp-config.js.map +1 -1
- package/dist/npm-installation.js +2 -2
- package/dist/npm-installation.js.map +1 -1
- package/dist/owned-files.d.ts +14 -4
- package/dist/owned-files.d.ts.map +1 -1
- package/dist/owned-files.js +24 -9
- package/dist/owned-files.js.map +1 -1
- package/dist/platform.d.ts +26 -3
- package/dist/platform.d.ts.map +1 -1
- package/dist/platform.js +87 -20
- package/dist/platform.js.map +1 -1
- package/dist/pty.d.ts +2 -1
- package/dist/pty.d.ts.map +1 -1
- package/dist/pty.js +20 -7
- package/dist/pty.js.map +1 -1
- package/dist/recovery-cloud-receipt.d.ts.map +1 -1
- package/dist/recovery-cloud-receipt.js +2 -1
- 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 +2 -1
- package/dist/recovery-cloud-workspace.js.map +1 -1
- package/dist/recovery-cloud.d.ts.map +1 -1
- package/dist/recovery-cloud.js +8 -9
- package/dist/recovery-cloud.js.map +1 -1
- package/dist/recovery-copilot.d.ts.map +1 -1
- package/dist/recovery-copilot.js +3 -2
- package/dist/recovery-copilot.js.map +1 -1
- package/dist/recovery-desktop.d.ts.map +1 -1
- package/dist/recovery-desktop.js +3 -2
- package/dist/recovery-desktop.js.map +1 -1
- package/dist/recovery-hooks.js +2 -2
- package/dist/recovery-hooks.js.map +1 -1
- package/dist/recovery-hosts.d.ts.map +1 -1
- package/dist/recovery-hosts.js +53 -6
- package/dist/recovery-hosts.js.map +1 -1
- package/dist/recovery-polytoken.d.ts +2 -2
- package/dist/recovery-polytoken.d.ts.map +1 -1
- package/dist/recovery-polytoken.js +13 -8
- package/dist/recovery-polytoken.js.map +1 -1
- package/dist/recovery-remote.js +3 -3
- package/dist/recovery-remote.js.map +1 -1
- package/dist/recovery-state.d.ts +22 -1
- package/dist/recovery-state.d.ts.map +1 -1
- package/dist/recovery-state.js +167 -31
- package/dist/recovery-state.js.map +1 -1
- package/dist/recovery-transfer.d.ts.map +1 -1
- package/dist/recovery-transfer.js +8 -4
- package/dist/recovery-transfer.js.map +1 -1
- package/dist/recovery.d.ts.map +1 -1
- package/dist/recovery.js +30 -11
- package/dist/recovery.js.map +1 -1
- package/dist/reliability-store.d.ts +13 -0
- package/dist/reliability-store.d.ts.map +1 -1
- package/dist/reliability-store.js +110 -8
- package/dist/reliability-store.js.map +1 -1
- package/dist/remember.d.ts.map +1 -1
- package/dist/remember.js +2 -2
- package/dist/remember.js.map +1 -1
- package/dist/retained-context.d.ts.map +1 -1
- package/dist/retained-context.js +3 -2
- package/dist/retained-context.js.map +1 -1
- package/dist/retrieval-task.d.ts +30 -0
- package/dist/retrieval-task.d.ts.map +1 -1
- package/dist/retrieval-task.js +98 -2
- package/dist/retrieval-task.js.map +1 -1
- package/dist/review.d.ts +1 -1
- package/dist/review.d.ts.map +1 -1
- package/dist/review.js +5 -2
- package/dist/review.js.map +1 -1
- package/dist/runtime.d.ts +4 -1
- package/dist/runtime.d.ts.map +1 -1
- package/dist/runtime.js +138 -91
- package/dist/runtime.js.map +1 -1
- package/dist/self-host.d.ts +1 -1
- package/dist/self-host.d.ts.map +1 -1
- package/dist/self-host.js +105 -23
- package/dist/self-host.js.map +1 -1
- package/dist/share.d.ts.map +1 -1
- package/dist/share.js +2 -1
- package/dist/share.js.map +1 -1
- package/dist/signed-snapshot.d.ts +7 -0
- package/dist/signed-snapshot.d.ts.map +1 -1
- package/dist/signed-snapshot.js +27 -0
- package/dist/signed-snapshot.js.map +1 -1
- package/dist/skill-command.d.ts +5 -2
- package/dist/skill-command.d.ts.map +1 -1
- package/dist/skill-command.js +73 -10
- package/dist/skill-command.js.map +1 -1
- package/dist/skill-guidance.js +1 -1
- package/dist/skill-guidance.js.map +1 -1
- package/dist/skills.d.ts.map +1 -1
- package/dist/skills.js +9 -7
- package/dist/skills.js.map +1 -1
- package/dist/status-health.d.ts +26 -0
- package/dist/status-health.d.ts.map +1 -0
- package/dist/status-health.js +49 -0
- package/dist/status-health.js.map +1 -0
- package/dist/telemetry.d.ts.map +1 -1
- package/dist/telemetry.js +2 -2
- package/dist/telemetry.js.map +1 -1
- package/dist/terminal-output.d.ts +76 -0
- package/dist/terminal-output.d.ts.map +1 -0
- package/dist/terminal-output.js +151 -0
- package/dist/terminal-output.js.map +1 -0
- package/dist/uninstall.d.ts.map +1 -1
- package/dist/uninstall.js +3 -1
- package/dist/uninstall.js.map +1 -1
- package/dist/update.d.ts +3 -0
- package/dist/update.d.ts.map +1 -1
- package/dist/update.js +40 -14
- package/dist/update.js.map +1 -1
- package/dist/windows-permissions.d.ts +6 -0
- package/dist/windows-permissions.d.ts.map +1 -1
- package/dist/windows-permissions.js +22 -2
- package/dist/windows-permissions.js.map +1 -1
- package/dist/windows-pty.d.ts.map +1 -1
- package/dist/windows-pty.js +4 -3
- package/dist/windows-pty.js.map +1 -1
- package/package.json +4 -4
package/dist/runtime.js
CHANGED
|
@@ -2,10 +2,13 @@ import { redactSecrets } from "./runtime-redaction.js";
|
|
|
2
2
|
export { redactSecrets } from "./runtime-redaction.js";
|
|
3
3
|
import { createHash, randomUUID } from "node:crypto";
|
|
4
4
|
import { CAPTURE_CONFLICT_MESSAGES } from "@lore-co/core/capture-conflict";
|
|
5
|
-
import {
|
|
5
|
+
import { guardItemLine } from "@lore-co/core/learning-line";
|
|
6
|
+
import { fitSkillContext } from "@lore-co/core/skill-context";
|
|
7
|
+
import { systemCommand } from "@lore-co/core/system-command";
|
|
8
|
+
import { approachGuidance, approachWorkflowRequested } from "./approach-guidance.js";
|
|
6
9
|
import { skillGuidance } from "./skill-guidance.js";
|
|
7
10
|
import { retainedContextTexts, retainedReceiptIds, rememberDeliveredContext } from "./retained-context.js";
|
|
8
|
-
import { clearRetrievalTask, resolveRetrievalTask } from "./retrieval-task.js";
|
|
11
|
+
import { clearRetrievalTask, forgetUncapturedPromptText, resolveRetrievalTask, unsavedRetrievalTask } from "./retrieval-task.js";
|
|
9
12
|
import { access, chmod, mkdir, readFile, stat, } from "node:fs/promises";
|
|
10
13
|
import { constants as fsConstants, realpathSync } from "node:fs";
|
|
11
14
|
import { execFile as execFileCallback } from "node:child_process";
|
|
@@ -16,10 +19,10 @@ import { promisify } from "node:util";
|
|
|
16
19
|
import { boundedUtf8Text, defaultGitPushBranches, repositoryRemoteScope, repositoryScopeFromGitRoot, } from "./repository.js";
|
|
17
20
|
import { ReliabilityStore, ReliabilityStoreError, atomicWriteJson as durableAtomicWriteJson, classifyRetryFailure, createIntegrationInvocationAttempt, durableUnlink, recordLocalGuardMetric, recordLocalRetrievalMetric, } from "./reliability-store.js";
|
|
18
21
|
import { writeInvocationAttemptBounded, writeInvocationCompletionBounded, } from "./invocation-health-writer.js";
|
|
19
|
-
import { SignedSnapshotError, verifySignedSnapshot, } from "./signed-snapshot.js";
|
|
22
|
+
import { SignedSnapshotError, referencesSourceConversations, verifySignedSnapshot, } from "./signed-snapshot.js";
|
|
20
23
|
import { CONTEXT_CACHE_MAX_STALE_MS, isContextTransportFailure, selectCachedContext, } from "./context-fallback.js";
|
|
21
24
|
import { RUNTIME_VERSION } from "./runtime-version.js";
|
|
22
|
-
import { checkLoreAccess, checkDirectory, checkWorkspaceRepository, readLocalConfig, resolveLocalConnection, bindLocalSession } from "./directory-policy.js";
|
|
25
|
+
import { checkLoreAccess, checkDirectory, checkWorkspaceRepository, readLocalConfig, resolveLocalConnection, bindLocalSession, loreDisabled, RECAP_REMINDER } from "./directory-policy.js";
|
|
23
26
|
import { capturePendingImages, enqueueEventImages, flushImageAttachment, hostMessageImages, rememberImageCapture } from "./image-capture.js";
|
|
24
27
|
import { guardRepositoryOverride, guardRuleConflict, guardToolTrigger, supportedGuardRule, guardEmergencyCommand, emergencyOverrideApplies } from "@lore-co/core/guard-evaluation";
|
|
25
28
|
const IS_STANDALONE_RUNTIME = typeof __LORE_STANDALONE__ === "boolean" && __LORE_STANDALONE__;
|
|
@@ -368,6 +371,7 @@ async function readRuntimeConfig(home, cwd) {
|
|
|
368
371
|
agents,
|
|
369
372
|
...(timeoutMs === undefined ? {} : { timeoutMs }),
|
|
370
373
|
capture: saved?.capture !== "off",
|
|
374
|
+
...(saved?.recapReminder === "off" ? { recapReminder: false } : {}),
|
|
371
375
|
};
|
|
372
376
|
}
|
|
373
377
|
catch {
|
|
@@ -479,7 +483,7 @@ async function repositoryScope(cwd, repository = repositoryRemoteScope) {
|
|
|
479
483
|
}
|
|
480
484
|
async function gitOutput(root, args) {
|
|
481
485
|
try {
|
|
482
|
-
const result = await execFile("git", ["-C", root, ...args], {
|
|
486
|
+
const result = await execFile(systemCommand("git", { exclude: [root] }), ["-C", root, ...args], {
|
|
483
487
|
encoding: "utf8",
|
|
484
488
|
timeout: 500,
|
|
485
489
|
maxBuffer: MAX_GIT_DIFF_BYTES * 2,
|
|
@@ -612,7 +616,11 @@ function createPromptObservationRequest(input, agent, sessionId, now, gitContext
|
|
|
612
616
|
connector: "lore-cli",
|
|
613
617
|
agent,
|
|
614
618
|
sessionId,
|
|
615
|
-
|
|
619
|
+
// Hosts keep one prompt or turn ID for messages queued while the agent is
|
|
620
|
+
// still working, so the ID alone does not identify a prompt. The key also
|
|
621
|
+
// covers the text: a repeated hook keeps its event and a queued message
|
|
622
|
+
// gets its own, instead of conflicting with the first one.
|
|
623
|
+
eventId: deterministicUuid(`lore-prompt\0${agent}\0${sessionId}\0${idempotencyKey}`),
|
|
616
624
|
prompt: redactSecrets(prompt),
|
|
617
625
|
...(promptId === undefined ? {} : { promptId }),
|
|
618
626
|
timestamp: now.toISOString(),
|
|
@@ -804,8 +812,10 @@ function deliveryFromResponse(value) {
|
|
|
804
812
|
context: boundedContext(context),
|
|
805
813
|
...(typeof value.relatedContext === "string" ? { relatedContext: redactSecrets(value.relatedContext).slice(0, 2_500) }
|
|
806
814
|
: isObject(value.context) && typeof value.context.relatedContext === "string" ? { relatedContext: redactSecrets(value.context.relatedContext).slice(0, 2_500) } : {}),
|
|
807
|
-
...(typeof value.skillContext === "string"
|
|
808
|
-
: isObject(value.context) && typeof value.context.skillContext === "string"
|
|
815
|
+
...(typeof value.skillContext === "string" ? { skillContext: fitSkillContext(redactSecrets(value.skillContext)) }
|
|
816
|
+
: isObject(value.context) && typeof value.context.skillContext === "string" ? { skillContext: fitSkillContext(redactSecrets(value.context.skillContext)) } : {}),
|
|
817
|
+
...(typeof value.approachContext === "string" ? { approachContext: redactSecrets(value.approachContext).slice(0, 600) }
|
|
818
|
+
: isObject(value.context) && typeof value.context.approachContext === "string" ? { approachContext: redactSecrets(value.context.approachContext).slice(0, 600) } : {}),
|
|
809
819
|
completeContext: context.length <= MAX_CONTEXT_CHARS,
|
|
810
820
|
...(receiptId === undefined ? {} : { receiptId }),
|
|
811
821
|
learned,
|
|
@@ -918,6 +928,7 @@ function deliveryFromSignedContext(payload, expectedRequest) {
|
|
|
918
928
|
context: payload.context,
|
|
919
929
|
relatedContext: payload.relatedContext,
|
|
920
930
|
skillContext: payload.skillContext,
|
|
931
|
+
approachContext: payload.approachContext,
|
|
921
932
|
receipt: { id: payload.receiptId },
|
|
922
933
|
memories: payload.memories,
|
|
923
934
|
hits: payload.hits,
|
|
@@ -1180,6 +1191,7 @@ async function postTurn(config, request, fetchImplementation, store) {
|
|
|
1180
1191
|
headers: {
|
|
1181
1192
|
"x-lore-context": "related-work-v1",
|
|
1182
1193
|
"x-lore-skills": "skills-v1",
|
|
1194
|
+
"x-lore-approaches": "approaches-v1",
|
|
1183
1195
|
accept: runtimeAccept(config),
|
|
1184
1196
|
authorization: `Bearer ${config.token}`,
|
|
1185
1197
|
"content-type": "application/json",
|
|
@@ -1249,11 +1261,13 @@ async function postTurn(config, request, fetchImplementation, store) {
|
|
|
1249
1261
|
task: (request.task ?? request.currentUser.content).trim(),
|
|
1250
1262
|
},
|
|
1251
1263
|
});
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1264
|
+
if (!referencesSourceConversations(payload)) {
|
|
1265
|
+
await store
|
|
1266
|
+
.writeContextSnapshot(request.eventId, {
|
|
1267
|
+
compactJws: value.contextSnapshot,
|
|
1268
|
+
})
|
|
1269
|
+
.catch(() => undefined);
|
|
1270
|
+
}
|
|
1257
1271
|
return { ...delivery, reliability };
|
|
1258
1272
|
}
|
|
1259
1273
|
async function postPromptObservation(config, request, fetchImplementation, _store) {
|
|
@@ -1334,6 +1348,7 @@ async function getPromptContext(config, agent, sessionId, sourceEventId, prompt,
|
|
|
1334
1348
|
headers: {
|
|
1335
1349
|
"x-lore-context": "related-work-v1",
|
|
1336
1350
|
"x-lore-skills": "skills-v1",
|
|
1351
|
+
"x-lore-approaches": "approaches-v1",
|
|
1337
1352
|
accept: runtimeAccept(config),
|
|
1338
1353
|
authorization: `Bearer ${config.token}`,
|
|
1339
1354
|
"content-type": "application/json",
|
|
@@ -1388,9 +1403,13 @@ async function getPromptContext(config, agent, sessionId, sourceEventId, prompt,
|
|
|
1388
1403
|
};
|
|
1389
1404
|
}
|
|
1390
1405
|
const delivery = deliveryFromSignedContext(payload, deliveryRequest);
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1406
|
+
// The signed snapshot carries the request, prompt included, so capture off
|
|
1407
|
+
// caches nothing. Offline fallback uses context cached while capture was on.
|
|
1408
|
+
if (config.capture && !referencesSourceConversations(payload)) {
|
|
1409
|
+
await store
|
|
1410
|
+
.writeContextSnapshot(sourceEventId, { compactJws: value.snapshot })
|
|
1411
|
+
.catch(() => undefined);
|
|
1412
|
+
}
|
|
1394
1413
|
return {
|
|
1395
1414
|
...delivery,
|
|
1396
1415
|
reliability,
|
|
@@ -1437,8 +1456,9 @@ async function getCachedPromptContext(config, prompt, gitContext, store, now, re
|
|
|
1437
1456
|
now,
|
|
1438
1457
|
}).payload;
|
|
1439
1458
|
// Signed payloads cannot be redacted in place. Retire legacy snapshots
|
|
1440
|
-
// containing raw request secrets and let a
|
|
1441
|
-
|
|
1459
|
+
// containing raw request secrets or source conversations and let a
|
|
1460
|
+
// later live request replace them.
|
|
1461
|
+
if (!hasRedactedContextRequest(payload) || referencesSourceConversations(payload)) {
|
|
1442
1462
|
await store.deleteContextSnapshot(record.cacheKey, value.compactJws);
|
|
1443
1463
|
continue;
|
|
1444
1464
|
}
|
|
@@ -1740,7 +1760,7 @@ function receiptMessage(config, agent, delivery) {
|
|
|
1740
1760
|
const path = delivery.receiptId === undefined
|
|
1741
1761
|
? learned === undefined
|
|
1742
1762
|
? undefined
|
|
1743
|
-
: `/
|
|
1763
|
+
: `/learnings/${encodeURIComponent(learned.id)}`
|
|
1744
1764
|
: `/receipts/${encodeURIComponent(delivery.receiptId)}`;
|
|
1745
1765
|
if (path !== undefined) {
|
|
1746
1766
|
parts.push(`${learned?.status === "proposed" ? "Review proposal" : "Review or forget"}: ${config.dashboardUrl}${path}`);
|
|
@@ -1817,23 +1837,13 @@ function guardResultFromResponse(value) {
|
|
|
1817
1837
|
if (!isObject(item) || typeof item.content !== "string") {
|
|
1818
1838
|
return [];
|
|
1819
1839
|
}
|
|
1820
|
-
const scope = isObject(item.scope) ? item.scope : {};
|
|
1821
|
-
const source = isObject(item.source) ? item.source : {};
|
|
1822
|
-
const explanation = guardExplanationFromValue(item.explanation);
|
|
1823
1840
|
return [
|
|
1824
1841
|
{
|
|
1825
1842
|
content: redactSecrets(item.content),
|
|
1826
1843
|
enforcement: typeof item.enforcement === "string"
|
|
1827
1844
|
? item.enforcement
|
|
1828
1845
|
: "advisory",
|
|
1829
|
-
|
|
1830
|
-
...(typeof scope.organization === "string"
|
|
1831
|
-
? { organization: scope.organization }
|
|
1832
|
-
: {}),
|
|
1833
|
-
...(typeof source.agent === "string"
|
|
1834
|
-
? { sourceAgent: source.agent }
|
|
1835
|
-
: {}),
|
|
1836
|
-
...(explanation === undefined ? {} : { explanation }),
|
|
1846
|
+
scope: guardItemScope(item),
|
|
1837
1847
|
},
|
|
1838
1848
|
];
|
|
1839
1849
|
})
|
|
@@ -1856,48 +1866,23 @@ function guardResultFromResponse(value) {
|
|
|
1856
1866
|
conflictSummaries,
|
|
1857
1867
|
};
|
|
1858
1868
|
}
|
|
1859
|
-
|
|
1860
|
-
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
|
|
1867
|
-
|
|
1868
|
-
|
|
1869
|
-
|
|
1870
|
-
|
|
1871
|
-
|
|
1872
|
-
|
|
1873
|
-
: undefined,
|
|
1874
|
-
typeof scope.project === "string" ? `project ${scope.project}` : undefined,
|
|
1875
|
-
typeof scope.repo === "string" ? `repo ${scope.repo}` : undefined,
|
|
1876
|
-
typeof scope.path === "string" ? `path ${scope.path}` : undefined,
|
|
1877
|
-
typeof scope.component === "string"
|
|
1878
|
-
? `component ${scope.component}`
|
|
1879
|
-
: undefined,
|
|
1880
|
-
].filter((part) => part !== undefined);
|
|
1881
|
-
const session = typeof value.provenance.sessionId === "string"
|
|
1882
|
-
? ` session ${value.provenance.sessionId}`
|
|
1883
|
-
: "";
|
|
1884
|
-
return `Why: ${redactSecrets(value.summary)} Source: ${value.provenance.agent}${session}. Scope: ${scopeParts.join(" · ") || "workspace-wide"}. Status: ${value.status}. Updated: ${value.freshness.updatedAt.slice(0, 10)}.`;
|
|
1885
|
-
}
|
|
1886
|
-
function guardItemSource(item) {
|
|
1887
|
-
const scope = item.repo !== undefined
|
|
1888
|
-
? `${item.repo} repo`
|
|
1889
|
-
: item.organization !== undefined
|
|
1890
|
-
? "organization rule"
|
|
1891
|
-
: "workspace knowledge";
|
|
1892
|
-
return item.sourceAgent === undefined
|
|
1893
|
-
? scope
|
|
1894
|
-
: `${scope} · ${item.sourceAgent} session`;
|
|
1895
|
-
}
|
|
1896
|
-
function guardAssistContext(result) {
|
|
1869
|
+
/** Where the item applies, as the explanation (or the item) reports it. */
|
|
1870
|
+
function guardItemScope(item) {
|
|
1871
|
+
const scope = isObject(item.explanation) && isObject(item.explanation.scope)
|
|
1872
|
+
? item.explanation.scope
|
|
1873
|
+
: isObject(item.scope)
|
|
1874
|
+
? item.scope
|
|
1875
|
+
: {};
|
|
1876
|
+
return {
|
|
1877
|
+
...(typeof scope.repo === "string" ? { repo: scope.repo } : {}),
|
|
1878
|
+
...(typeof scope.path === "string" ? { path: scope.path } : {}),
|
|
1879
|
+
...(typeof scope.component === "string" ? { component: scope.component } : {}),
|
|
1880
|
+
};
|
|
1881
|
+
}
|
|
1882
|
+
function guardAssistContext(result, repo) {
|
|
1897
1883
|
const lines = ["Relevant Lore context:"];
|
|
1898
1884
|
for (const item of result.items) {
|
|
1899
|
-
lines.push(`- ${item
|
|
1900
|
-
lines.push(` ${item.explanation ?? `Source: ${guardItemSource(item)}.`}`);
|
|
1885
|
+
lines.push(`- ${guardItemLine(item, repo)}`);
|
|
1901
1886
|
}
|
|
1902
1887
|
if (result.conflictSummaries.length > 0) {
|
|
1903
1888
|
lines.push("", "Lore policy notes:");
|
|
@@ -1907,7 +1892,7 @@ function guardAssistContext(result) {
|
|
|
1907
1892
|
}
|
|
1908
1893
|
return boundedContext(lines.join("\n"));
|
|
1909
1894
|
}
|
|
1910
|
-
function guardConfirmationReason(result) {
|
|
1895
|
+
function guardConfirmationReason(result, repo) {
|
|
1911
1896
|
if (result.policyUnavailable)
|
|
1912
1897
|
return "Lore Guard cannot evaluate an existing required rule. An owner or admin must rewrite it using a supported prohibition or change it to advisory before retrying.";
|
|
1913
1898
|
const required = result.items.filter((item) => item.enforcement === "required");
|
|
@@ -1915,10 +1900,7 @@ function guardConfirmationReason(result) {
|
|
|
1915
1900
|
const lines = [
|
|
1916
1901
|
result.items.length === 0 ? "Lore Guard cannot verify the enforcement policy." : "Lore Guard: this action conflicts with a required rule.",
|
|
1917
1902
|
...result.conflictSummaries,
|
|
1918
|
-
...shown.
|
|
1919
|
-
`- ${item.content}`,
|
|
1920
|
-
` ${item.explanation ?? `Source: ${guardItemSource(item)}.`}`,
|
|
1921
|
-
]),
|
|
1903
|
+
...shown.map((item) => `- ${guardItemLine(item, repo)}`),
|
|
1922
1904
|
];
|
|
1923
1905
|
lines.push(result.offline && result.confirmCommand !== undefined
|
|
1924
1906
|
? `Restore Lore or run: ${result.confirmCommand}`
|
|
@@ -2009,7 +1991,7 @@ async function offlineGuard(config, store, request, now, previouslyEnforced, all
|
|
|
2009
1991
|
}) !== null) : [];
|
|
2010
1992
|
return {
|
|
2011
1993
|
offline: true, mode: "enforce", requiresConfirmation: rules.length > 0,
|
|
2012
|
-
items: rules.map((rule) => ({ content: String(rule.content), enforcement: "required" })),
|
|
1994
|
+
items: rules.map((rule) => ({ content: String(rule.content), enforcement: "required", scope: guardItemScope(rule) })),
|
|
2013
1995
|
conflictSummaries: ["Lore is offline; this action was checked against its verified, unexpired saved policy."],
|
|
2014
1996
|
confirmCommand: guardEmergencyCommand(String(request.sessionId), String(request.repo ?? "")),
|
|
2015
1997
|
};
|
|
@@ -2131,7 +2113,7 @@ async function handleGuardEvent(input, agent, config, sessionId, options, now, c
|
|
|
2131
2113
|
// independently of `requiresConfirmation`: the producer reports it via
|
|
2132
2114
|
// `policyIssue` rather than `confirm`, so it never sets `requiresConfirmation`.
|
|
2133
2115
|
if (result.requiresConfirmation || result.policyUnavailable) {
|
|
2134
|
-
const reason = guardConfirmationReason(result);
|
|
2116
|
+
const reason = guardConfirmationReason(result, scope?.repo);
|
|
2135
2117
|
if (agent === "polytoken")
|
|
2136
2118
|
return { outcome: "deny", reason };
|
|
2137
2119
|
if (agent === "copilot-cli") {
|
|
@@ -2166,8 +2148,11 @@ async function handleGuardEvent(input, agent, config, sessionId, options, now, c
|
|
|
2166
2148
|
// context was already added by userPromptTransformed.
|
|
2167
2149
|
return undefined;
|
|
2168
2150
|
}
|
|
2169
|
-
if (agent === "polytoken")
|
|
2170
|
-
|
|
2151
|
+
if (agent === "polytoken") {
|
|
2152
|
+
// Like every other host, Polytoken gets no verdict on advisory results:
|
|
2153
|
+
// Lore only ever blocks, it never approves an action.
|
|
2154
|
+
return undefined;
|
|
2155
|
+
}
|
|
2171
2156
|
if (agent === "cursor") {
|
|
2172
2157
|
// Cursor cannot inject agent context at the shell boundary on allow.
|
|
2173
2158
|
return undefined;
|
|
@@ -2175,11 +2160,14 @@ async function handleGuardEvent(input, agent, config, sessionId, options, now, c
|
|
|
2175
2160
|
return {
|
|
2176
2161
|
hookSpecificOutput: {
|
|
2177
2162
|
hookEventName: "PreToolUse",
|
|
2178
|
-
additionalContext: guardAssistContext(result),
|
|
2163
|
+
additionalContext: guardAssistContext(result, scope?.repo),
|
|
2179
2164
|
},
|
|
2180
2165
|
};
|
|
2181
2166
|
}
|
|
2182
2167
|
export async function handleHookEvent(value, agent, options = {}) {
|
|
2168
|
+
// LORE_DISABLE: the same empty answer as an unconnected machine, for every host and event.
|
|
2169
|
+
if (loreDisabled(options.environment ?? process.env))
|
|
2170
|
+
return undefined;
|
|
2183
2171
|
if (!isObject(value)) {
|
|
2184
2172
|
throw new InvalidHookInputError();
|
|
2185
2173
|
}
|
|
@@ -2255,6 +2243,8 @@ export async function handleHookEvent(value, agent, options = {}) {
|
|
|
2255
2243
|
: "Before answering each user request or using other tools, call lore_context with that sessionId and the full user message as prompt. This retrieves relevant shared knowledge; automatic capture is paused, so nothing is recorded.",
|
|
2256
2244
|
"Use the returned context when relevant. If retrieval fails, report that limitation; do not invent shared knowledge.",
|
|
2257
2245
|
...(capture ? ["After completing each response, call lore_complete with the same sessionId and a concise response summary so the next user correction retains the prior attempt. These Lore calls are available through MCP and require no shell commands."] : ["These Lore calls are available through MCP and require no shell commands."]),
|
|
2246
|
+
// Cursor's session start runs once per conversation, so no marker is needed.
|
|
2247
|
+
...(recapReminderApplies(agent, capture, config) ? [RECAP_REMINDER] : []),
|
|
2258
2248
|
].join("\n") };
|
|
2259
2249
|
}
|
|
2260
2250
|
if (eventName === "Stop" || eventName === "AssistantResponse") {
|
|
@@ -2323,27 +2313,69 @@ export async function handleHookEvent(value, agent, options = {}) {
|
|
|
2323
2313
|
};
|
|
2324
2314
|
}
|
|
2325
2315
|
}
|
|
2316
|
+
const promptConfig = { ...config, capture };
|
|
2317
|
+
// Once per store: remove prompt text earlier versions kept with capture off.
|
|
2318
|
+
await runtimeStore(config, options.home)
|
|
2319
|
+
.then(store => forgetUncapturedPromptText(store, capture, now, CURSOR_SESSION_PREFIX))
|
|
2320
|
+
.catch(() => undefined);
|
|
2326
2321
|
if (agent === "cursor") {
|
|
2327
2322
|
const store = await runtimeStore(config, options.home);
|
|
2328
2323
|
const existing = await store.readState(cursorSessionKey(sessionId));
|
|
2324
|
+
const { prompt: _prompt, ...kept } = isObject(existing) ? existing : {};
|
|
2329
2325
|
const cwd = stringField(input.cwd);
|
|
2326
|
+
// Capture off keeps no prompt: lore_context then searches with the
|
|
2327
|
+
// message the agent passes it.
|
|
2330
2328
|
if (cwd)
|
|
2331
2329
|
await store.writeState(cursorSessionKey(sessionId), {
|
|
2332
|
-
...
|
|
2333
|
-
...(stringField(input.prompt) ? { prompt: redactSecrets(String(input.prompt)) } : {}),
|
|
2330
|
+
...kept, sessionId, cwd,
|
|
2331
|
+
...(capture && stringField(input.prompt) ? { prompt: redactSecrets(String(input.prompt)) } : {}),
|
|
2334
2332
|
...(stringField(input.prompt_id) ? { promptId: input.prompt_id } : {}), updatedAt: now.toISOString(),
|
|
2335
2333
|
});
|
|
2336
2334
|
// Cursor's prompt hook supports submission control only. The MCP call
|
|
2337
2335
|
// delivers the context; capture here retains native editor event identity.
|
|
2338
2336
|
// Retrieve is skipped so the notice cooldown is not primed by a discarded
|
|
2339
2337
|
// result: the MCP `lore_context` call surfaces the reliability notice.
|
|
2340
|
-
await handlePromptEvent(input, agent, sessionId,
|
|
2338
|
+
await handlePromptEvent(input, agent, sessionId, promptConfig, options, now, capture, false);
|
|
2341
2339
|
return { continue: true };
|
|
2342
2340
|
}
|
|
2343
|
-
return handlePromptEvent(input, agent, sessionId,
|
|
2341
|
+
return handlePromptEvent(input, agent, sessionId, promptConfig, options, now, capture, true, recapReminderApplies(agent, capture, config));
|
|
2342
|
+
}
|
|
2343
|
+
export { RECAP_REMINDER };
|
|
2344
|
+
/** Hosts where `lore connect` installs the recap skill and this runtime delivers context. */
|
|
2345
|
+
const RECAP_HOSTS = new Set(["claude", "codex", "copilot-cli", "copilot-vscode", "cursor"]);
|
|
2346
|
+
function recapReminderApplies(agent, capture, config) {
|
|
2347
|
+
return !capture && config.recapReminder !== false && RECAP_HOSTS.has(agent);
|
|
2348
|
+
}
|
|
2349
|
+
/** True for the first prompt of a session only. The marker is a hash of the session, never the prompt. */
|
|
2350
|
+
/** True once this session asked to save an approach. The marker is a hash of the session, never the prompt. */
|
|
2351
|
+
async function approachWorkflowStarted(store, agent, sessionId, now, requested) {
|
|
2352
|
+
const key = `approach-workflow-${sha256(`${agent}\0${sessionId}`)}`;
|
|
2353
|
+
try {
|
|
2354
|
+
if (!requested)
|
|
2355
|
+
return await store.readState(key) !== null;
|
|
2356
|
+
await store.updateState(key, (current) => current ?? { at: now.toISOString() }, now, { recoverCorrupt: true });
|
|
2357
|
+
}
|
|
2358
|
+
catch {
|
|
2359
|
+
// An unreadable marker only costs the reminder after compaction.
|
|
2360
|
+
}
|
|
2361
|
+
return requested;
|
|
2362
|
+
}
|
|
2363
|
+
async function claimRecapReminder(store, agent, sessionId, now) {
|
|
2364
|
+
let first = false;
|
|
2365
|
+
try {
|
|
2366
|
+
await store.updateState(`recap-reminder-${sha256(`${agent}\0${sessionId}`)}`, (current) => {
|
|
2367
|
+
first = current === null;
|
|
2368
|
+
return current ?? { at: now.toISOString() };
|
|
2369
|
+
}, now, { recoverCorrupt: true });
|
|
2370
|
+
}
|
|
2371
|
+
catch {
|
|
2372
|
+
return false;
|
|
2373
|
+
}
|
|
2374
|
+
return first;
|
|
2344
2375
|
}
|
|
2376
|
+
const CURSOR_SESSION_PREFIX = "cursor-session-";
|
|
2345
2377
|
function cursorSessionKey(sessionId) {
|
|
2346
|
-
return
|
|
2378
|
+
return `${CURSOR_SESSION_PREFIX}${sha256(sessionId)}`;
|
|
2347
2379
|
}
|
|
2348
2380
|
/** MCP requests are bound to a session/workspace observed by the native hook. */
|
|
2349
2381
|
async function cursorSession(sessionId, options) {
|
|
@@ -2371,7 +2403,7 @@ async function cursorSession(sessionId, options) {
|
|
|
2371
2403
|
}
|
|
2372
2404
|
// Same definition as the native hooks: local mode and the dashboard switches.
|
|
2373
2405
|
const capture = config.capture && access.capturePausedBy === undefined;
|
|
2374
|
-
return { config, store, state, cwd: stringField(state.cwd), capture };
|
|
2406
|
+
return { config: { ...config, capture }, store, state, cwd: stringField(state.cwd), capture };
|
|
2375
2407
|
}
|
|
2376
2408
|
export async function getCursorContext(sessionId, prompt, options = {}) {
|
|
2377
2409
|
const { config, state, cwd, capture } = await cursorSession(sessionId, options);
|
|
@@ -2390,7 +2422,7 @@ export async function completeCursorResponse(sessionId, response, options = {})
|
|
|
2390
2422
|
const { prompt: _prompt, promptId: _promptId, ...rest } = state;
|
|
2391
2423
|
await store.writeState(cursorSessionKey(sessionId), { ...rest, updatedAt: now.toISOString() });
|
|
2392
2424
|
}
|
|
2393
|
-
async function handlePromptEvent(input, agent, sessionId, config, options, now, capture = true, retrieve = true) {
|
|
2425
|
+
async function handlePromptEvent(input, agent, sessionId, config, options, now, capture = true, retrieve = true, recapReminder = false) {
|
|
2394
2426
|
const fetchImplementation = options.fetch ?? globalThis.fetch;
|
|
2395
2427
|
const prompt = stringField(input.prompt);
|
|
2396
2428
|
if (prompt === undefined) {
|
|
@@ -2466,11 +2498,14 @@ async function handlePromptEvent(input, agent, sessionId, config, options, now,
|
|
|
2466
2498
|
};
|
|
2467
2499
|
const lineage = await readLineageMetadata(options.home, options.environment ?? process.env);
|
|
2468
2500
|
let delivery = emptyDelivery();
|
|
2469
|
-
const
|
|
2501
|
+
const retrievalSession = (repo) => ({
|
|
2470
2502
|
agent, sessionId, connectionKey: credentialReliabilityWorkspaceKey(config),
|
|
2471
2503
|
cwd: stringField(input.cwd), repo,
|
|
2472
|
-
}
|
|
2473
|
-
|
|
2504
|
+
});
|
|
2505
|
+
const retrievalTask = (repo) => !config.capture
|
|
2506
|
+
? unsavedRetrievalTask(store, retrievalSession(repo), prompt)
|
|
2507
|
+
: resolveRetrievalTask(store, retrievalSession(repo), prompt, now, pending?.cwd === stringField(input.cwd) && pending
|
|
2508
|
+
? { content: pending.assistantMessage, capturedAt: pending.capturedAt } : undefined);
|
|
2474
2509
|
if (pending !== null) {
|
|
2475
2510
|
let receivedTurnContext = false;
|
|
2476
2511
|
let request = await createTurnRequest(input, agent, pending, now, lineage);
|
|
@@ -2648,8 +2683,11 @@ async function handlePromptEvent(input, agent, sessionId, config, options, now,
|
|
|
2648
2683
|
await rememberDeliveredContext(store, contextSession, delivery).catch(() => undefined);
|
|
2649
2684
|
const relatedContext = delivery.relatedContext && !retainedContext.some(text => text.includes(delivery.relatedContext))
|
|
2650
2685
|
? delivery.relatedContext : undefined;
|
|
2686
|
+
const approachContext = delivery.approachContext && !retainedContext.some(text => text.includes(delivery.approachContext))
|
|
2687
|
+
? delivery.approachContext : undefined;
|
|
2651
2688
|
// Skills are fresh on every turn, even when learning context is retained.
|
|
2652
|
-
const taskContext = [delivery.skillContext, delivery.context, relatedContext, delivery.reliability?.status === "failed" ? undefined : approachGuidance(prompt, agent, redactSecrets(sessionId), retainedContext), delivery.skillContext === undefined && delivery.reliability?.status !== "failed" ? skillGuidance(retainedContext) : undefined
|
|
2689
|
+
const taskContext = [delivery.skillContext, delivery.context, relatedContext, approachContext, delivery.reliability?.status === "failed" ? undefined : approachGuidance(prompt, agent, redactSecrets(sessionId), retainedContext, await approachWorkflowStarted(store, agent, sessionId, now, approachWorkflowRequested(prompt))), delivery.skillContext === undefined && delivery.reliability?.status !== "failed" ? skillGuidance(retainedContext) : undefined,
|
|
2690
|
+
recapReminder && await claimRecapReminder(store, agent, sessionId, now) ? RECAP_REMINDER : undefined].filter(Boolean).join("\n\n");
|
|
2653
2691
|
const systemMessage = agent === "cursor" || agent === "polytoken"
|
|
2654
2692
|
? undefined
|
|
2655
2693
|
: [receipt, reliabilityNotice].filter(Boolean).join(" · ") || undefined;
|
|
@@ -2738,6 +2776,15 @@ export async function runHook(args = process.argv.slice(2)) {
|
|
|
2738
2776
|
if (agent === null) {
|
|
2739
2777
|
return;
|
|
2740
2778
|
}
|
|
2779
|
+
if (loreDisabled(process.env)) {
|
|
2780
|
+
// Nothing is read, sent, or written, not even invocation health. Drain the
|
|
2781
|
+
// payload so the host never sees a broken pipe, then exit 0 with no output.
|
|
2782
|
+
try {
|
|
2783
|
+
for await (const _chunk of process.stdin) { /* discard */ }
|
|
2784
|
+
}
|
|
2785
|
+
catch { /* nothing to report */ }
|
|
2786
|
+
return;
|
|
2787
|
+
}
|
|
2741
2788
|
let tracker = null;
|
|
2742
2789
|
let input;
|
|
2743
2790
|
try {
|