@lore-co/cli 0.2.26 → 0.2.28
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 +67 -20
- 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-repair.d.ts +11 -0
- package/dist/capture-repair.d.ts.map +1 -0
- package/dist/capture-repair.js +94 -0
- package/dist/capture-repair.js.map +1 -0
- 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 +114 -6
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +628 -127
- 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 +3 -2
- package/dist/command-help.d.ts.map +1 -1
- package/dist/command-help.js +11 -5
- 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/drafts.d.ts +8 -0
- package/dist/drafts.d.ts.map +1 -0
- package/dist/drafts.js +173 -0
- package/dist/drafts.js.map +1 -0
- package/dist/generated-assets.d.ts +9 -9
- package/dist/generated-assets.d.ts.map +1 -1
- package/dist/generated-assets.js +9 -9
- package/dist/generated-assets.js.map +1 -1
- package/dist/github.d.ts.map +1 -1
- package/dist/github.js +46 -14
- 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 +29 -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 +5 -2
- package/dist/recovery.d.ts.map +1 -1
- package/dist/recovery.js +37 -14
- 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 +32 -3
- 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 +12 -7
- 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 +147 -97
- 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,12 +19,12 @@ 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
|
-
import { guardRepositoryOverride, guardRuleConflict, guardToolTrigger, supportedGuardRule, guardEmergencyCommand, emergencyOverrideApplies } from "@lore-co/core/guard-evaluation";
|
|
27
|
+
import { guardRepositoryOverride, guardRuleConflict, guardRuleContent, guardToolTrigger, isGuardRule, supportedGuardRule, guardEmergencyCommand, emergencyOverrideApplies } from "@lore-co/core/guard-evaluation";
|
|
25
28
|
const IS_STANDALONE_RUNTIME = typeof __LORE_STANDALONE__ === "boolean" && __LORE_STANDALONE__;
|
|
26
29
|
export const COMMAND_HOOK_AGENT_NAMES = [
|
|
27
30
|
"codex",
|
|
@@ -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
|
}
|
|
@@ -1536,8 +1556,12 @@ async function retrievalNotice(delivery, sessionId, store, now) {
|
|
|
1536
1556
|
if (reliability.fallback === "cached_context") {
|
|
1537
1557
|
return `Lore used verified ${reliability.freshness.state} cached context (${formatAge(reliability.freshness.ageMs)} old) because live retrieval was unavailable.`;
|
|
1538
1558
|
}
|
|
1559
|
+
// Keyword-only retrieval is a normal setup; mention it only when semantic search is down.
|
|
1560
|
+
if (reliability.fallback === "live_lexical" && !reliability.reasons.includes("semantic_unavailable")) {
|
|
1561
|
+
return undefined;
|
|
1562
|
+
}
|
|
1539
1563
|
const notice = reliability.fallback === "live_lexical"
|
|
1540
|
-
? "Lore
|
|
1564
|
+
? "Lore matched learnings by keyword because semantic search is unavailable right now."
|
|
1541
1565
|
: reliability.reasons.includes("invalid_response")
|
|
1542
1566
|
? "Lore context could not be trusted; continuing without injected context."
|
|
1543
1567
|
: "Lore context is temporarily unavailable; continuing without injected context.";
|
|
@@ -1729,9 +1753,6 @@ function receiptMessage(config, agent, delivery) {
|
|
|
1729
1753
|
? preview(delivered.content)
|
|
1730
1754
|
: `${preview(delivered.content)} (+${delivery.delivered.length - 1} more)`;
|
|
1731
1755
|
parts.push(`Lore taught ${label}${delivered.path === undefined ? "" : ` for ${delivered.path}`}: ${suffix}`);
|
|
1732
|
-
if (delivered.reasons !== undefined) {
|
|
1733
|
-
parts.push(`Why: ${delivered.reasons.join(", ")}`);
|
|
1734
|
-
}
|
|
1735
1756
|
}
|
|
1736
1757
|
if (parts.length === 0) {
|
|
1737
1758
|
return undefined;
|
|
@@ -1740,7 +1761,7 @@ function receiptMessage(config, agent, delivery) {
|
|
|
1740
1761
|
const path = delivery.receiptId === undefined
|
|
1741
1762
|
? learned === undefined
|
|
1742
1763
|
? undefined
|
|
1743
|
-
: `/
|
|
1764
|
+
: `/learnings/${encodeURIComponent(learned.id)}`
|
|
1744
1765
|
: `/receipts/${encodeURIComponent(delivery.receiptId)}`;
|
|
1745
1766
|
if (path !== undefined) {
|
|
1746
1767
|
parts.push(`${learned?.status === "proposed" ? "Review proposal" : "Review or forget"}: ${config.dashboardUrl}${path}`);
|
|
@@ -1817,23 +1838,15 @@ function guardResultFromResponse(value) {
|
|
|
1817
1838
|
if (!isObject(item) || typeof item.content !== "string") {
|
|
1818
1839
|
return [];
|
|
1819
1840
|
}
|
|
1820
|
-
const
|
|
1821
|
-
const source = isObject(item.source) ? item.source : {};
|
|
1822
|
-
const explanation = guardExplanationFromValue(item.explanation);
|
|
1841
|
+
const rule = isGuardRule(item.guardRule) ? guardRuleContent(item.guardRule) : undefined;
|
|
1823
1842
|
return [
|
|
1824
1843
|
{
|
|
1825
1844
|
content: redactSecrets(item.content),
|
|
1845
|
+
...(rule === undefined || rule === item.content ? {} : { rule }),
|
|
1826
1846
|
enforcement: typeof item.enforcement === "string"
|
|
1827
1847
|
? item.enforcement
|
|
1828
1848
|
: "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 }),
|
|
1849
|
+
scope: guardItemScope(item),
|
|
1837
1850
|
},
|
|
1838
1851
|
];
|
|
1839
1852
|
})
|
|
@@ -1856,48 +1869,23 @@ function guardResultFromResponse(value) {
|
|
|
1856
1869
|
conflictSummaries,
|
|
1857
1870
|
};
|
|
1858
1871
|
}
|
|
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) {
|
|
1872
|
+
/** Where the item applies, as the explanation (or the item) reports it. */
|
|
1873
|
+
function guardItemScope(item) {
|
|
1874
|
+
const scope = isObject(item.explanation) && isObject(item.explanation.scope)
|
|
1875
|
+
? item.explanation.scope
|
|
1876
|
+
: isObject(item.scope)
|
|
1877
|
+
? item.scope
|
|
1878
|
+
: {};
|
|
1879
|
+
return {
|
|
1880
|
+
...(typeof scope.repo === "string" ? { repo: scope.repo } : {}),
|
|
1881
|
+
...(typeof scope.path === "string" ? { path: scope.path } : {}),
|
|
1882
|
+
...(typeof scope.component === "string" ? { component: scope.component } : {}),
|
|
1883
|
+
};
|
|
1884
|
+
}
|
|
1885
|
+
function guardAssistContext(result, repo) {
|
|
1897
1886
|
const lines = ["Relevant Lore context:"];
|
|
1898
1887
|
for (const item of result.items) {
|
|
1899
|
-
lines.push(`- ${item
|
|
1900
|
-
lines.push(` ${item.explanation ?? `Source: ${guardItemSource(item)}.`}`);
|
|
1888
|
+
lines.push(`- ${guardItemLine(item, repo)}`);
|
|
1901
1889
|
}
|
|
1902
1890
|
if (result.conflictSummaries.length > 0) {
|
|
1903
1891
|
lines.push("", "Lore policy notes:");
|
|
@@ -1907,18 +1895,15 @@ function guardAssistContext(result) {
|
|
|
1907
1895
|
}
|
|
1908
1896
|
return boundedContext(lines.join("\n"));
|
|
1909
1897
|
}
|
|
1910
|
-
function guardConfirmationReason(result) {
|
|
1898
|
+
function guardConfirmationReason(result, repo) {
|
|
1911
1899
|
if (result.policyUnavailable)
|
|
1912
1900
|
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
1901
|
const required = result.items.filter((item) => item.enforcement === "required");
|
|
1914
1902
|
const shown = required.length > 0 ? required : result.items;
|
|
1915
1903
|
const lines = [
|
|
1916
|
-
result.items.length === 0 ? "Lore Guard cannot verify the enforcement policy." : "Lore Guard: this action
|
|
1904
|
+
result.items.length === 0 ? "Lore Guard cannot verify the enforcement policy." : "Lore Guard: this action needs approval under a required rule.",
|
|
1917
1905
|
...result.conflictSummaries,
|
|
1918
|
-
...shown.
|
|
1919
|
-
`- ${item.content}`,
|
|
1920
|
-
` ${item.explanation ?? `Source: ${guardItemSource(item)}.`}`,
|
|
1921
|
-
]),
|
|
1906
|
+
...shown.map((item) => `- ${guardItemLine(item, repo)}${item.rule === undefined ? "" : ` Rule: ${item.rule}`}`),
|
|
1922
1907
|
];
|
|
1923
1908
|
lines.push(result.offline && result.confirmCommand !== undefined
|
|
1924
1909
|
? `Restore Lore or run: ${result.confirmCommand}`
|
|
@@ -2009,7 +1994,7 @@ async function offlineGuard(config, store, request, now, previouslyEnforced, all
|
|
|
2009
1994
|
}) !== null) : [];
|
|
2010
1995
|
return {
|
|
2011
1996
|
offline: true, mode: "enforce", requiresConfirmation: rules.length > 0,
|
|
2012
|
-
items: rules.map((rule) => ({ content: String(rule.content), enforcement: "required" })),
|
|
1997
|
+
items: rules.map((rule) => ({ content: String(rule.content), enforcement: "required", scope: guardItemScope(rule) })),
|
|
2013
1998
|
conflictSummaries: ["Lore is offline; this action was checked against its verified, unexpired saved policy."],
|
|
2014
1999
|
confirmCommand: guardEmergencyCommand(String(request.sessionId), String(request.repo ?? "")),
|
|
2015
2000
|
};
|
|
@@ -2131,7 +2116,7 @@ async function handleGuardEvent(input, agent, config, sessionId, options, now, c
|
|
|
2131
2116
|
// independently of `requiresConfirmation`: the producer reports it via
|
|
2132
2117
|
// `policyIssue` rather than `confirm`, so it never sets `requiresConfirmation`.
|
|
2133
2118
|
if (result.requiresConfirmation || result.policyUnavailable) {
|
|
2134
|
-
const reason = guardConfirmationReason(result);
|
|
2119
|
+
const reason = guardConfirmationReason(result, scope?.repo);
|
|
2135
2120
|
if (agent === "polytoken")
|
|
2136
2121
|
return { outcome: "deny", reason };
|
|
2137
2122
|
if (agent === "copilot-cli") {
|
|
@@ -2166,8 +2151,11 @@ async function handleGuardEvent(input, agent, config, sessionId, options, now, c
|
|
|
2166
2151
|
// context was already added by userPromptTransformed.
|
|
2167
2152
|
return undefined;
|
|
2168
2153
|
}
|
|
2169
|
-
if (agent === "polytoken")
|
|
2170
|
-
|
|
2154
|
+
if (agent === "polytoken") {
|
|
2155
|
+
// Like every other host, Polytoken gets no verdict on advisory results:
|
|
2156
|
+
// Lore only ever blocks, it never approves an action.
|
|
2157
|
+
return undefined;
|
|
2158
|
+
}
|
|
2171
2159
|
if (agent === "cursor") {
|
|
2172
2160
|
// Cursor cannot inject agent context at the shell boundary on allow.
|
|
2173
2161
|
return undefined;
|
|
@@ -2175,11 +2163,14 @@ async function handleGuardEvent(input, agent, config, sessionId, options, now, c
|
|
|
2175
2163
|
return {
|
|
2176
2164
|
hookSpecificOutput: {
|
|
2177
2165
|
hookEventName: "PreToolUse",
|
|
2178
|
-
additionalContext: guardAssistContext(result),
|
|
2166
|
+
additionalContext: guardAssistContext(result, scope?.repo),
|
|
2179
2167
|
},
|
|
2180
2168
|
};
|
|
2181
2169
|
}
|
|
2182
2170
|
export async function handleHookEvent(value, agent, options = {}) {
|
|
2171
|
+
// LORE_DISABLE: the same empty answer as an unconnected machine, for every host and event.
|
|
2172
|
+
if (loreDisabled(options.environment ?? process.env))
|
|
2173
|
+
return undefined;
|
|
2183
2174
|
if (!isObject(value)) {
|
|
2184
2175
|
throw new InvalidHookInputError();
|
|
2185
2176
|
}
|
|
@@ -2255,6 +2246,8 @@ export async function handleHookEvent(value, agent, options = {}) {
|
|
|
2255
2246
|
: "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
2247
|
"Use the returned context when relevant. If retrieval fails, report that limitation; do not invent shared knowledge.",
|
|
2257
2248
|
...(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."]),
|
|
2249
|
+
// Cursor's session start runs once per conversation, so no marker is needed.
|
|
2250
|
+
...(recapReminderApplies(agent, capture, config) ? [RECAP_REMINDER] : []),
|
|
2258
2251
|
].join("\n") };
|
|
2259
2252
|
}
|
|
2260
2253
|
if (eventName === "Stop" || eventName === "AssistantResponse") {
|
|
@@ -2323,27 +2316,69 @@ export async function handleHookEvent(value, agent, options = {}) {
|
|
|
2323
2316
|
};
|
|
2324
2317
|
}
|
|
2325
2318
|
}
|
|
2319
|
+
const promptConfig = { ...config, capture };
|
|
2320
|
+
// Once per store: remove prompt text earlier versions kept with capture off.
|
|
2321
|
+
await runtimeStore(config, options.home)
|
|
2322
|
+
.then(store => forgetUncapturedPromptText(store, capture, now, CURSOR_SESSION_PREFIX))
|
|
2323
|
+
.catch(() => undefined);
|
|
2326
2324
|
if (agent === "cursor") {
|
|
2327
2325
|
const store = await runtimeStore(config, options.home);
|
|
2328
2326
|
const existing = await store.readState(cursorSessionKey(sessionId));
|
|
2327
|
+
const { prompt: _prompt, ...kept } = isObject(existing) ? existing : {};
|
|
2329
2328
|
const cwd = stringField(input.cwd);
|
|
2329
|
+
// Capture off keeps no prompt: lore_context then searches with the
|
|
2330
|
+
// message the agent passes it.
|
|
2330
2331
|
if (cwd)
|
|
2331
2332
|
await store.writeState(cursorSessionKey(sessionId), {
|
|
2332
|
-
...
|
|
2333
|
-
...(stringField(input.prompt) ? { prompt: redactSecrets(String(input.prompt)) } : {}),
|
|
2333
|
+
...kept, sessionId, cwd,
|
|
2334
|
+
...(capture && stringField(input.prompt) ? { prompt: redactSecrets(String(input.prompt)) } : {}),
|
|
2334
2335
|
...(stringField(input.prompt_id) ? { promptId: input.prompt_id } : {}), updatedAt: now.toISOString(),
|
|
2335
2336
|
});
|
|
2336
2337
|
// Cursor's prompt hook supports submission control only. The MCP call
|
|
2337
2338
|
// delivers the context; capture here retains native editor event identity.
|
|
2338
2339
|
// Retrieve is skipped so the notice cooldown is not primed by a discarded
|
|
2339
2340
|
// result: the MCP `lore_context` call surfaces the reliability notice.
|
|
2340
|
-
await handlePromptEvent(input, agent, sessionId,
|
|
2341
|
+
await handlePromptEvent(input, agent, sessionId, promptConfig, options, now, capture, false);
|
|
2341
2342
|
return { continue: true };
|
|
2342
2343
|
}
|
|
2343
|
-
return handlePromptEvent(input, agent, sessionId,
|
|
2344
|
+
return handlePromptEvent(input, agent, sessionId, promptConfig, options, now, capture, true, recapReminderApplies(agent, capture, config));
|
|
2345
|
+
}
|
|
2346
|
+
export { RECAP_REMINDER };
|
|
2347
|
+
/** Hosts where `lore connect` installs the recap skill and this runtime delivers context. */
|
|
2348
|
+
const RECAP_HOSTS = new Set(["claude", "codex", "copilot-cli", "copilot-vscode", "cursor"]);
|
|
2349
|
+
function recapReminderApplies(agent, capture, config) {
|
|
2350
|
+
return !capture && config.recapReminder !== false && RECAP_HOSTS.has(agent);
|
|
2351
|
+
}
|
|
2352
|
+
/** True for the first prompt of a session only. The marker is a hash of the session, never the prompt. */
|
|
2353
|
+
/** True once this session asked to save an approach. The marker is a hash of the session, never the prompt. */
|
|
2354
|
+
async function approachWorkflowStarted(store, agent, sessionId, now, requested) {
|
|
2355
|
+
const key = `approach-workflow-${sha256(`${agent}\0${sessionId}`)}`;
|
|
2356
|
+
try {
|
|
2357
|
+
if (!requested)
|
|
2358
|
+
return await store.readState(key) !== null;
|
|
2359
|
+
await store.updateState(key, (current) => current ?? { at: now.toISOString() }, now, { recoverCorrupt: true });
|
|
2360
|
+
}
|
|
2361
|
+
catch {
|
|
2362
|
+
// An unreadable marker only costs the reminder after compaction.
|
|
2363
|
+
}
|
|
2364
|
+
return requested;
|
|
2365
|
+
}
|
|
2366
|
+
async function claimRecapReminder(store, agent, sessionId, now) {
|
|
2367
|
+
let first = false;
|
|
2368
|
+
try {
|
|
2369
|
+
await store.updateState(`recap-reminder-${sha256(`${agent}\0${sessionId}`)}`, (current) => {
|
|
2370
|
+
first = current === null;
|
|
2371
|
+
return current ?? { at: now.toISOString() };
|
|
2372
|
+
}, now, { recoverCorrupt: true });
|
|
2373
|
+
}
|
|
2374
|
+
catch {
|
|
2375
|
+
return false;
|
|
2376
|
+
}
|
|
2377
|
+
return first;
|
|
2344
2378
|
}
|
|
2379
|
+
const CURSOR_SESSION_PREFIX = "cursor-session-";
|
|
2345
2380
|
function cursorSessionKey(sessionId) {
|
|
2346
|
-
return
|
|
2381
|
+
return `${CURSOR_SESSION_PREFIX}${sha256(sessionId)}`;
|
|
2347
2382
|
}
|
|
2348
2383
|
/** MCP requests are bound to a session/workspace observed by the native hook. */
|
|
2349
2384
|
async function cursorSession(sessionId, options) {
|
|
@@ -2371,7 +2406,7 @@ async function cursorSession(sessionId, options) {
|
|
|
2371
2406
|
}
|
|
2372
2407
|
// Same definition as the native hooks: local mode and the dashboard switches.
|
|
2373
2408
|
const capture = config.capture && access.capturePausedBy === undefined;
|
|
2374
|
-
return { config, store, state, cwd: stringField(state.cwd), capture };
|
|
2409
|
+
return { config: { ...config, capture }, store, state, cwd: stringField(state.cwd), capture };
|
|
2375
2410
|
}
|
|
2376
2411
|
export async function getCursorContext(sessionId, prompt, options = {}) {
|
|
2377
2412
|
const { config, state, cwd, capture } = await cursorSession(sessionId, options);
|
|
@@ -2390,7 +2425,7 @@ export async function completeCursorResponse(sessionId, response, options = {})
|
|
|
2390
2425
|
const { prompt: _prompt, promptId: _promptId, ...rest } = state;
|
|
2391
2426
|
await store.writeState(cursorSessionKey(sessionId), { ...rest, updatedAt: now.toISOString() });
|
|
2392
2427
|
}
|
|
2393
|
-
async function handlePromptEvent(input, agent, sessionId, config, options, now, capture = true, retrieve = true) {
|
|
2428
|
+
async function handlePromptEvent(input, agent, sessionId, config, options, now, capture = true, retrieve = true, recapReminder = false) {
|
|
2394
2429
|
const fetchImplementation = options.fetch ?? globalThis.fetch;
|
|
2395
2430
|
const prompt = stringField(input.prompt);
|
|
2396
2431
|
if (prompt === undefined) {
|
|
@@ -2466,11 +2501,14 @@ async function handlePromptEvent(input, agent, sessionId, config, options, now,
|
|
|
2466
2501
|
};
|
|
2467
2502
|
const lineage = await readLineageMetadata(options.home, options.environment ?? process.env);
|
|
2468
2503
|
let delivery = emptyDelivery();
|
|
2469
|
-
const
|
|
2504
|
+
const retrievalSession = (repo) => ({
|
|
2470
2505
|
agent, sessionId, connectionKey: credentialReliabilityWorkspaceKey(config),
|
|
2471
2506
|
cwd: stringField(input.cwd), repo,
|
|
2472
|
-
}
|
|
2473
|
-
|
|
2507
|
+
});
|
|
2508
|
+
const retrievalTask = (repo) => !config.capture
|
|
2509
|
+
? unsavedRetrievalTask(store, retrievalSession(repo), prompt)
|
|
2510
|
+
: resolveRetrievalTask(store, retrievalSession(repo), prompt, now, pending?.cwd === stringField(input.cwd) && pending
|
|
2511
|
+
? { content: pending.assistantMessage, capturedAt: pending.capturedAt } : undefined);
|
|
2474
2512
|
if (pending !== null) {
|
|
2475
2513
|
let receivedTurnContext = false;
|
|
2476
2514
|
let request = await createTurnRequest(input, agent, pending, now, lineage);
|
|
@@ -2648,8 +2686,11 @@ async function handlePromptEvent(input, agent, sessionId, config, options, now,
|
|
|
2648
2686
|
await rememberDeliveredContext(store, contextSession, delivery).catch(() => undefined);
|
|
2649
2687
|
const relatedContext = delivery.relatedContext && !retainedContext.some(text => text.includes(delivery.relatedContext))
|
|
2650
2688
|
? delivery.relatedContext : undefined;
|
|
2689
|
+
const approachContext = delivery.approachContext && !retainedContext.some(text => text.includes(delivery.approachContext))
|
|
2690
|
+
? delivery.approachContext : undefined;
|
|
2651
2691
|
// 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
|
|
2692
|
+
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,
|
|
2693
|
+
recapReminder && await claimRecapReminder(store, agent, sessionId, now) ? RECAP_REMINDER : undefined].filter(Boolean).join("\n\n");
|
|
2653
2694
|
const systemMessage = agent === "cursor" || agent === "polytoken"
|
|
2654
2695
|
? undefined
|
|
2655
2696
|
: [receipt, reliabilityNotice].filter(Boolean).join(" · ") || undefined;
|
|
@@ -2738,6 +2779,15 @@ export async function runHook(args = process.argv.slice(2)) {
|
|
|
2738
2779
|
if (agent === null) {
|
|
2739
2780
|
return;
|
|
2740
2781
|
}
|
|
2782
|
+
if (loreDisabled(process.env)) {
|
|
2783
|
+
// Nothing is read, sent, or written, not even invocation health. Drain the
|
|
2784
|
+
// payload so the host never sees a broken pipe, then exit 0 with no output.
|
|
2785
|
+
try {
|
|
2786
|
+
for await (const _chunk of process.stdin) { /* discard */ }
|
|
2787
|
+
}
|
|
2788
|
+
catch { /* nothing to report */ }
|
|
2789
|
+
return;
|
|
2790
|
+
}
|
|
2741
2791
|
let tracker = null;
|
|
2742
2792
|
let input;
|
|
2743
2793
|
try {
|