@lore-co/cli 0.1.18 → 0.1.21
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 +70 -5
- package/dist/cli.d.ts +37 -2
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +593 -49
- package/dist/cli.js.map +1 -1
- package/dist/cursor-mcp.d.ts +5 -0
- package/dist/cursor-mcp.d.ts.map +1 -0
- package/dist/cursor-mcp.js +41 -0
- package/dist/cursor-mcp.js.map +1 -0
- package/dist/devin-client.d.ts.map +1 -1
- package/dist/devin-client.js +6 -3
- package/dist/devin-client.js.map +1 -1
- 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.d.ts.map +1 -1
- package/dist/guard.js +31 -1
- package/dist/guard.js.map +1 -1
- package/dist/remember.d.ts +3 -0
- package/dist/remember.d.ts.map +1 -0
- package/dist/remember.js +159 -0
- package/dist/remember.js.map +1 -0
- package/dist/runtime.d.ts +15 -3
- package/dist/runtime.d.ts.map +1 -1
- package/dist/runtime.js +319 -110
- package/dist/runtime.js.map +1 -1
- package/dist/self-host.d.ts +15 -2
- package/dist/self-host.d.ts.map +1 -1
- package/dist/self-host.js +55 -8
- package/dist/self-host.js.map +1 -1
- package/dist/update.js +3 -3
- package/package.json +4 -3
package/dist/runtime.js
CHANGED
|
@@ -12,10 +12,13 @@ import { writeInvocationAttemptBounded, writeInvocationCompletionBounded, } from
|
|
|
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 { guardRuleConflict, guardToolTrigger, supportedGuardRule, guardEmergencyCommand, emergencyOverrideApplies } from "@lore-co/core/guard-evaluation";
|
|
15
16
|
const IS_STANDALONE_RUNTIME = typeof __LORE_STANDALONE__ === "boolean" && __LORE_STANDALONE__;
|
|
16
17
|
export const COMMAND_HOOK_AGENT_NAMES = [
|
|
17
18
|
"codex",
|
|
18
19
|
"claude",
|
|
20
|
+
"copilot-cli",
|
|
21
|
+
"copilot-vscode",
|
|
19
22
|
"cursor",
|
|
20
23
|
"polytoken",
|
|
21
24
|
];
|
|
@@ -125,24 +128,72 @@ function stringField(value) {
|
|
|
125
128
|
function normalizeHookInput(input, agent, environment) {
|
|
126
129
|
const rawEventName = stringField(input.hook_event_name) ??
|
|
127
130
|
stringField(input.event) ??
|
|
131
|
+
(agent === "copilot-cli"
|
|
132
|
+
? stringField(environment.LORE_HOOK_EVENT)
|
|
133
|
+
: undefined) ??
|
|
128
134
|
(agent === "polytoken"
|
|
129
135
|
? stringField(environment.POLYTOKEN_HOOK_EVENT)
|
|
130
136
|
: undefined);
|
|
131
|
-
if (agent !== "cursor" &&
|
|
137
|
+
if (agent !== "cursor" &&
|
|
138
|
+
agent !== "polytoken" &&
|
|
139
|
+
agent !== "copilot-cli") {
|
|
132
140
|
return {
|
|
133
141
|
input,
|
|
134
142
|
eventName: rawEventName,
|
|
135
143
|
sessionId: stringField(input.session_id),
|
|
136
144
|
};
|
|
137
145
|
}
|
|
146
|
+
if (agent === "copilot-cli") {
|
|
147
|
+
// CLI command hooks encode toolArgs as JSON; SDK-style inputs are objects.
|
|
148
|
+
let toolArgs = input.toolArgs;
|
|
149
|
+
if (typeof toolArgs === "string") {
|
|
150
|
+
try {
|
|
151
|
+
toolArgs = JSON.parse(toolArgs);
|
|
152
|
+
}
|
|
153
|
+
catch { /* Invalid input stays invalid. */ }
|
|
154
|
+
}
|
|
155
|
+
const eventName = rawEventName === "userPromptTransformed"
|
|
156
|
+
? "UserPromptSubmit"
|
|
157
|
+
: rawEventName === "preToolUse"
|
|
158
|
+
? "PreToolUse"
|
|
159
|
+
: rawEventName === "agentStop"
|
|
160
|
+
? "Stop"
|
|
161
|
+
: rawEventName === "sessionEnd"
|
|
162
|
+
? "SessionEnd"
|
|
163
|
+
: rawEventName;
|
|
164
|
+
const sessionId = stringField(input.sessionId) ?? stringField(input.session_id);
|
|
165
|
+
return {
|
|
166
|
+
input: {
|
|
167
|
+
...input,
|
|
168
|
+
...(rawEventName === undefined ? {} : { event: rawEventName }),
|
|
169
|
+
...(sessionId === undefined ? {} : { session_id: sessionId }),
|
|
170
|
+
...(stringField(input.transformedPrompt) === undefined
|
|
171
|
+
? {}
|
|
172
|
+
: { transformed_prompt: input.transformedPrompt }),
|
|
173
|
+
...(eventName === "PreToolUse"
|
|
174
|
+
? {
|
|
175
|
+
tool_name: input.toolName,
|
|
176
|
+
tool_input: toolArgs,
|
|
177
|
+
}
|
|
178
|
+
: {}),
|
|
179
|
+
...(eventName === "Stop"
|
|
180
|
+
? { last_assistant_message: input.response }
|
|
181
|
+
: {}),
|
|
182
|
+
},
|
|
183
|
+
eventName,
|
|
184
|
+
sessionId,
|
|
185
|
+
};
|
|
186
|
+
}
|
|
138
187
|
if (agent === "polytoken") {
|
|
139
188
|
const eventName = rawEventName === "pre_user_prompt"
|
|
140
189
|
? "UserPromptSubmit"
|
|
141
190
|
: rawEventName === "post_model_turn"
|
|
142
191
|
? "AssistantResponse"
|
|
143
|
-
: rawEventName === "
|
|
144
|
-
? "
|
|
145
|
-
: rawEventName
|
|
192
|
+
: rawEventName === "pre_tool_use"
|
|
193
|
+
? "PreToolUse"
|
|
194
|
+
: rawEventName === "session_start"
|
|
195
|
+
? "SessionStart"
|
|
196
|
+
: rawEventName;
|
|
146
197
|
const sessionId = stringField(input.session_id) ??
|
|
147
198
|
stringField(environment.POLYTOKEN_SESSION_ID);
|
|
148
199
|
const cwd = stringField(input.cwd) ??
|
|
@@ -165,6 +216,7 @@ function normalizeHookInput(input, agent, environment) {
|
|
|
165
216
|
...input,
|
|
166
217
|
...(sessionId === undefined ? {} : { session_id: sessionId }),
|
|
167
218
|
...(cwd === undefined ? {} : { cwd }),
|
|
219
|
+
...(eventName === "PreToolUse" ? { tool_input: input.input ?? input.tool_input } : {}),
|
|
168
220
|
...(prompt === undefined ? {} : { prompt }),
|
|
169
221
|
...(assistantMessage === undefined
|
|
170
222
|
? {}
|
|
@@ -183,15 +235,17 @@ function normalizeHookInput(input, agent, environment) {
|
|
|
183
235
|
: [];
|
|
184
236
|
const cwd = stringField(input.cwd) ??
|
|
185
237
|
(workspaceRoots.length === 1 ? workspaceRoots[0] : undefined);
|
|
186
|
-
const eventName = rawEventName === "
|
|
187
|
-
? "
|
|
188
|
-
: rawEventName === "
|
|
189
|
-
? "
|
|
190
|
-
: rawEventName === "
|
|
191
|
-
? "
|
|
192
|
-
: rawEventName === "
|
|
193
|
-
? "
|
|
194
|
-
: rawEventName
|
|
238
|
+
const eventName = rawEventName === "sessionStart"
|
|
239
|
+
? "SessionStart"
|
|
240
|
+
: rawEventName === "beforeSubmitPrompt"
|
|
241
|
+
? "UserPromptSubmit"
|
|
242
|
+
: rawEventName === "afterAgentResponse"
|
|
243
|
+
? "AssistantResponse"
|
|
244
|
+
: rawEventName === "sessionEnd"
|
|
245
|
+
? "SessionEnd"
|
|
246
|
+
: rawEventName === "beforeShellExecution"
|
|
247
|
+
? "PreToolUse"
|
|
248
|
+
: rawEventName;
|
|
195
249
|
return {
|
|
196
250
|
input: {
|
|
197
251
|
...input,
|
|
@@ -225,9 +279,10 @@ class InvalidHookInputError extends Error {
|
|
|
225
279
|
}
|
|
226
280
|
function validHookInput(input, agent, eventName) {
|
|
227
281
|
const recognized = agent === "polytoken"
|
|
228
|
-
? eventName === "UserPromptSubmit" || eventName === "AssistantResponse"
|
|
282
|
+
? eventName === "UserPromptSubmit" || eventName === "AssistantResponse" || eventName === "PreToolUse"
|
|
229
283
|
: agent === "cursor"
|
|
230
|
-
? eventName === "
|
|
284
|
+
? eventName === "SessionStart" ||
|
|
285
|
+
eventName === "UserPromptSubmit" ||
|
|
231
286
|
eventName === "AssistantResponse" ||
|
|
232
287
|
eventName === "PreToolUse" ||
|
|
233
288
|
eventName === "SessionEnd"
|
|
@@ -242,6 +297,10 @@ function validHookInput(input, agent, eventName) {
|
|
|
242
297
|
return stringField(input.prompt) !== undefined;
|
|
243
298
|
}
|
|
244
299
|
if (eventName === "Stop" || eventName === "AssistantResponse") {
|
|
300
|
+
// Copilot agentStop may carry only transcript metadata. Never parse that
|
|
301
|
+
// transcript or invent assistant text; standalone prompt capture still works.
|
|
302
|
+
if (agent === "copilot-cli" && eventName === "Stop")
|
|
303
|
+
return true;
|
|
245
304
|
return stringField(input.last_assistant_message) !== undefined;
|
|
246
305
|
}
|
|
247
306
|
if (eventName === "PreToolUse") {
|
|
@@ -249,11 +308,8 @@ function validHookInput(input, agent, eventName) {
|
|
|
249
308
|
if (toolName === undefined || !isObject(input.tool_input)) {
|
|
250
309
|
return false;
|
|
251
310
|
}
|
|
252
|
-
if (
|
|
253
|
-
return
|
|
254
|
-
}
|
|
255
|
-
if (GUARD_SHELL_TOOLS.has(toolName)) {
|
|
256
|
-
return stringField(input.tool_input.command) !== undefined;
|
|
311
|
+
if (GUARD_SHELL_TOOLS.has(toolName.split(".").at(-1))) {
|
|
312
|
+
return stringField(input.tool_input.command ?? input.tool_input.cmd) !== undefined;
|
|
257
313
|
}
|
|
258
314
|
return true;
|
|
259
315
|
}
|
|
@@ -587,7 +643,7 @@ function createPromptObservationRequest(input, agent, sessionId, now, gitContext
|
|
|
587
643
|
connector: "lore-cli",
|
|
588
644
|
agent,
|
|
589
645
|
sessionId,
|
|
590
|
-
eventId: deterministicUuid(`lore-prompt\0${agent}\0${sessionId}\0${promptId ??
|
|
646
|
+
eventId: deterministicUuid(`lore-prompt\0${agent}\0${sessionId}\0${promptId ?? idempotencyKey}`),
|
|
591
647
|
prompt: redactSecrets(prompt),
|
|
592
648
|
...(promptId === undefined ? {} : { promptId }),
|
|
593
649
|
timestamp: now.toISOString(),
|
|
@@ -907,12 +963,12 @@ function runtimeAccept(config) {
|
|
|
907
963
|
}
|
|
908
964
|
async function runtimeStore(config, home) {
|
|
909
965
|
const store = new ReliabilityStore(reliabilityWorkspaceKey(config), {
|
|
910
|
-
|
|
966
|
+
home: homeDirectory(home),
|
|
911
967
|
});
|
|
912
968
|
await store.initialize();
|
|
913
969
|
await store.migrateLegacyQueue(queueDirectory(home));
|
|
914
970
|
if (config.workspaceId !== undefined) {
|
|
915
|
-
const credentialStore = new ReliabilityStore(credentialReliabilityWorkspaceKey(config), {
|
|
971
|
+
const credentialStore = new ReliabilityStore(credentialReliabilityWorkspaceKey(config), { home: homeDirectory(home) });
|
|
916
972
|
await credentialStore.transferPendingTo(store).catch(() => undefined);
|
|
917
973
|
}
|
|
918
974
|
return store;
|
|
@@ -1425,12 +1481,25 @@ function queuedRequest(value) {
|
|
|
1425
1481
|
async function flushOne(config, fetchImplementation, store) {
|
|
1426
1482
|
const claimed = await store.claimNext({
|
|
1427
1483
|
workerId: `native-hook:${process.pid}`,
|
|
1428
|
-
kinds: ["turn", "prompt"],
|
|
1484
|
+
kinds: ["turn", "prompt", "guard_override"],
|
|
1429
1485
|
});
|
|
1430
1486
|
if (claimed === null || claimed.claim === undefined) {
|
|
1431
1487
|
return null;
|
|
1432
1488
|
}
|
|
1433
1489
|
try {
|
|
1490
|
+
if (claimed.kind === "guard_override") {
|
|
1491
|
+
const response = await fetchImplementation(`${config.apiUrl.replace(/\/+$/u, "")}/v1/guard/overrides`, {
|
|
1492
|
+
method: "POST", headers: { authorization: `Bearer ${config.token}`, "content-type": "application/json" },
|
|
1493
|
+
body: JSON.stringify(claimed.payload), signal: AbortSignal.timeout(config.timeoutMs ?? 2_500),
|
|
1494
|
+
});
|
|
1495
|
+
if (!response.ok)
|
|
1496
|
+
throw new RuntimeHttpError("Override audit upload failed", response.status);
|
|
1497
|
+
const acknowledgement = await protocolResponseJson(response);
|
|
1498
|
+
if (!isObject(acknowledgement) || acknowledgement.overrideId !== claimed.idempotencyKey)
|
|
1499
|
+
throw new IncompatibleReliabilityError("Override acknowledgement is invalid");
|
|
1500
|
+
await store.acknowledgeClaim(claimed.claim.id);
|
|
1501
|
+
return { idempotencyKey: claimed.idempotencyKey, state: "acknowledged" };
|
|
1502
|
+
}
|
|
1434
1503
|
const queued = queuedRequest(claimed.payload);
|
|
1435
1504
|
if (queued === null) {
|
|
1436
1505
|
const failed = await store.failClaim({
|
|
@@ -1515,8 +1584,8 @@ function receiptMessage(config, agent, delivery) {
|
|
|
1515
1584
|
}
|
|
1516
1585
|
// ---------------------------------------------------------------------------
|
|
1517
1586
|
// Context Guard: proactive checks at tool boundaries (PreToolUse and Cursor
|
|
1518
|
-
// beforeShellExecution).
|
|
1519
|
-
//
|
|
1587
|
+
// beforeShellExecution). Assist degrades on outages; known Enforce uses a
|
|
1588
|
+
// verified saved policy or denies actions whose policy cannot be verified.
|
|
1520
1589
|
// ---------------------------------------------------------------------------
|
|
1521
1590
|
/** How long a locally cached "guard is off" verdict suppresses network calls. */
|
|
1522
1591
|
const GUARD_MODE_TTL_MS = 5 * 60_000;
|
|
@@ -1524,51 +1593,14 @@ const GUARD_MODE_TTL_MS = 5 * 60_000;
|
|
|
1524
1593
|
const GUARD_KEY_COOLDOWN_MS = 15 * 60_000;
|
|
1525
1594
|
const GUARD_MAX_CACHED_KEYS = 200;
|
|
1526
1595
|
const GUARD_TIMEOUT_MS = 4_000;
|
|
1527
|
-
const
|
|
1528
|
-
"Edit",
|
|
1529
|
-
"Write",
|
|
1530
|
-
"MultiEdit",
|
|
1531
|
-
"NotebookEdit",
|
|
1532
|
-
"ApplyPatch",
|
|
1533
|
-
"apply_patch",
|
|
1534
|
-
"edit",
|
|
1535
|
-
"write",
|
|
1536
|
-
"patch",
|
|
1537
|
-
"str_replace_editor",
|
|
1538
|
-
]);
|
|
1539
|
-
const GUARD_SHELL_TOOLS = new Set(["Bash", "bash", "Shell", "shell"]);
|
|
1540
|
-
const GUARD_COMMIT_COMMAND = /\bgit\s+(?:commit|push)\b/iu;
|
|
1541
|
-
const GUARD_DEPLOY_COMMAND = /\b(?:terraform\s+(?:apply|destroy)|pulumi\s+up|kubectl\s+(?:apply|delete|rollout)|helm\s+(?:install|upgrade|uninstall)|docker\s+push|fly(?:ctl)?\s+deploy|vercel(?:\s+deploy|\s+--prod)|railway\s+up|serverless\s+deploy|sls\s+deploy|cdk\s+deploy|eb\s+deploy|gcloud\s+(?:app|run|functions)\s+deploy|az\s+webapp\s+(?:up|deploy)|(?:npm|pnpm|yarn)\s+publish|prisma\s+migrate\s+deploy|drizzle-kit\s+(?:push|migrate))\b/iu;
|
|
1542
|
-
const GUARD_DESTRUCTIVE_COMMAND = /\b(?:rm\s+(?:-[a-z]*\s+)*-[a-z]*r[a-z]*f|drop\s+(?:table|database)|truncate\s+table)\b/iu;
|
|
1596
|
+
const GUARD_SHELL_TOOLS = new Set(["Bash", "bash", "Shell", "shell", "exec_command", "shell_command", "run_terminal_cmd", "shell_exec", "shell_monitor"]);
|
|
1543
1597
|
/**
|
|
1544
1598
|
* Decides whether a tool call is a meaningful action boundary. File edits
|
|
1545
1599
|
* always qualify; shell commands qualify only when they look high-impact
|
|
1546
1600
|
* (commit, push, deploy, publish, destructive) to keep the guard quiet.
|
|
1547
1601
|
*/
|
|
1548
1602
|
export function guardTrigger(toolName, toolInput) {
|
|
1549
|
-
|
|
1550
|
-
const files = [toolInput.file_path, toolInput.notebook_path, toolInput.path]
|
|
1551
|
-
.map((value) => stringField(value))
|
|
1552
|
-
.filter((value) => value !== undefined);
|
|
1553
|
-
return { action: "edit", ...(files.length === 0 ? {} : { files }) };
|
|
1554
|
-
}
|
|
1555
|
-
if (GUARD_SHELL_TOOLS.has(toolName)) {
|
|
1556
|
-
const command = stringField(toolInput.command);
|
|
1557
|
-
if (command === undefined) {
|
|
1558
|
-
return null;
|
|
1559
|
-
}
|
|
1560
|
-
if (GUARD_COMMIT_COMMAND.test(command)) {
|
|
1561
|
-
return { action: "commit", command };
|
|
1562
|
-
}
|
|
1563
|
-
if (GUARD_DEPLOY_COMMAND.test(command)) {
|
|
1564
|
-
return { action: "deploy", command };
|
|
1565
|
-
}
|
|
1566
|
-
if (GUARD_DESTRUCTIVE_COMMAND.test(command)) {
|
|
1567
|
-
return { action: "command", command };
|
|
1568
|
-
}
|
|
1569
|
-
return null;
|
|
1570
|
-
}
|
|
1571
|
-
return null;
|
|
1603
|
+
return guardToolTrigger(toolName, toolInput);
|
|
1572
1604
|
}
|
|
1573
1605
|
function guardStatePath(agent, sessionId, home) {
|
|
1574
1606
|
return resolve(loreDirectory(home), "state", "guard", `${sha256(`${agent}\0${sessionId}`)}.json`);
|
|
@@ -1649,6 +1681,7 @@ function guardResultFromResponse(value) {
|
|
|
1649
1681
|
return {
|
|
1650
1682
|
...(typeof value.checkId === "string" ? { checkId: value.checkId } : {}),
|
|
1651
1683
|
mode: value.mode,
|
|
1684
|
+
...(value.policyIssue === "unsupported_required_rules" ? { policyUnavailable: true } : {}),
|
|
1652
1685
|
requiresConfirmation: value.requiresConfirmation === true,
|
|
1653
1686
|
...(typeof confirm.command === "string"
|
|
1654
1687
|
? { confirmCommand: confirm.command }
|
|
@@ -1701,7 +1734,7 @@ function guardAssistContext(result) {
|
|
|
1701
1734
|
lines.push(` ${item.explanation ?? `Source: ${guardItemSource(item)}.`}`);
|
|
1702
1735
|
}
|
|
1703
1736
|
if (result.conflictSummaries.length > 0) {
|
|
1704
|
-
lines.push("", "
|
|
1737
|
+
lines.push("", "Lore policy notes:");
|
|
1705
1738
|
for (const summary of result.conflictSummaries) {
|
|
1706
1739
|
lines.push(`- ${summary}`);
|
|
1707
1740
|
}
|
|
@@ -1709,18 +1742,23 @@ function guardAssistContext(result) {
|
|
|
1709
1742
|
return boundedContext(lines.join("\n"));
|
|
1710
1743
|
}
|
|
1711
1744
|
function guardConfirmationReason(result) {
|
|
1745
|
+
if (result.policyUnavailable)
|
|
1746
|
+
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.";
|
|
1712
1747
|
const required = result.items.filter((item) => item.enforcement === "required");
|
|
1713
1748
|
const shown = required.length > 0 ? required : result.items;
|
|
1714
1749
|
const lines = [
|
|
1715
|
-
"Lore Guard: this action conflicts with a required rule.",
|
|
1750
|
+
result.items.length === 0 ? "Lore Guard cannot verify the enforcement policy." : "Lore Guard: this action conflicts with a required rule.",
|
|
1751
|
+
...result.conflictSummaries,
|
|
1716
1752
|
...shown.flatMap((item) => [
|
|
1717
1753
|
`- ${item.content}`,
|
|
1718
1754
|
` ${item.explanation ?? `Source: ${guardItemSource(item)}.`}`,
|
|
1719
1755
|
]),
|
|
1720
1756
|
];
|
|
1721
|
-
lines.push(result.confirmCommand
|
|
1722
|
-
?
|
|
1723
|
-
:
|
|
1757
|
+
lines.push(result.offline && result.confirmCommand !== undefined
|
|
1758
|
+
? `Restore Lore or run: ${result.confirmCommand}`
|
|
1759
|
+
: result.confirmCommand === undefined
|
|
1760
|
+
? "Ask the user to confirm before proceeding."
|
|
1761
|
+
: `Approve here to proceed, or the user can run: ${result.confirmCommand}`);
|
|
1724
1762
|
return boundedContext(lines.join("\n"));
|
|
1725
1763
|
}
|
|
1726
1764
|
async function postGuardCheck(config, request, fetchImplementation, store) {
|
|
@@ -1755,13 +1793,65 @@ async function postGuardCheck(config, request, fetchImplementation, store) {
|
|
|
1755
1793
|
throw new IncompatibleReliabilityError("Lore Guard snapshot does not bind the returned policy");
|
|
1756
1794
|
}
|
|
1757
1795
|
await store.writePolicySnapshot("current", value.policySnapshot);
|
|
1796
|
+
await store.writeState("guard-policy-state", { mode: payload.mode, overrides: payload.overrides, policyVersion: payload.policyVersion });
|
|
1758
1797
|
return guardResultFromResponse(value.check);
|
|
1759
1798
|
}
|
|
1760
|
-
async function
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
|
|
1799
|
+
async function offlineGuard(config, store, request, now, previouslyEnforced, allowCachedPolicy) {
|
|
1800
|
+
const remembered = await store.readState("guard-policy-state");
|
|
1801
|
+
const emergency = await store.readState("guard-emergency-override");
|
|
1802
|
+
if (emergencyOverrideApplies(emergency, String(request.sessionId), typeof request.repo === "string" ? request.repo : undefined, now)) {
|
|
1803
|
+
return { offline: true, mode: "enforce", requiresConfirmation: false, items: [], conflictSummaries: ["An audited, time-limited emergency override is active for this session and repository."] };
|
|
1764
1804
|
}
|
|
1805
|
+
const overrides = isObject(remembered) && Array.isArray(remembered.overrides) ? remembered.overrides : [];
|
|
1806
|
+
const override = overrides.find((item) => isObject(item) && item.repo === request.repo);
|
|
1807
|
+
const knownEnforce = previouslyEnforced || (isObject(override) ? override.mode : isObject(remembered) ? remembered.mode : undefined) === "enforce";
|
|
1808
|
+
try {
|
|
1809
|
+
if (!allowCachedPolicy)
|
|
1810
|
+
throw new Error("A live policy could not be authenticated");
|
|
1811
|
+
const compactJws = await store.readPolicySnapshot("current");
|
|
1812
|
+
const keys = await cachedSnapshotKeys(config, store, now);
|
|
1813
|
+
if (compactJws === null || keys === null)
|
|
1814
|
+
throw new Error("Policy unavailable");
|
|
1815
|
+
const payload = verifySignedSnapshot(compactJws, {
|
|
1816
|
+
workspaceId: store.workspaceId, keys, kind: "guard_policy", now,
|
|
1817
|
+
}).payload;
|
|
1818
|
+
const cachedOverride = Array.isArray(payload.overrides) ? payload.overrides.find((item) => isObject(item) && item.repo === request.repo) : undefined;
|
|
1819
|
+
const mode = isObject(cachedOverride) ? cachedOverride.mode : payload.mode;
|
|
1820
|
+
if (isObject(remembered) && remembered.policyVersion !== payload.policyVersion)
|
|
1821
|
+
throw new Error("Policy has changed");
|
|
1822
|
+
if (knownEnforce && mode !== "enforce")
|
|
1823
|
+
throw new Error("Cached policy cannot loosen known enforcement");
|
|
1824
|
+
if (mode !== "enforce")
|
|
1825
|
+
return null;
|
|
1826
|
+
if (!Array.isArray(payload.requiredRules) || payload.requiredRules.some((rule) => !isObject(rule) || !isObject(rule.scope) || typeof rule.content !== "string" || !supportedGuardRule(rule))) {
|
|
1827
|
+
throw new Error("Policy contains unsupported required rules");
|
|
1828
|
+
}
|
|
1829
|
+
const rules = Array.isArray(payload.requiredRules) ? payload.requiredRules.filter((rule) => isObject(rule) && isObject(rule.scope) && typeof rule.content === "string" &&
|
|
1830
|
+
guardRuleConflict(rule, {
|
|
1831
|
+
action: String(request.action),
|
|
1832
|
+
...(typeof request.repo === "string" ? { repo: request.repo } : {}),
|
|
1833
|
+
...(typeof request.path === "string" ? { path: request.path } : {}),
|
|
1834
|
+
...(typeof request.command === "string" ? { command: request.command } : {}),
|
|
1835
|
+
...(Array.isArray(request.files) ? { files: request.files.filter((file) => typeof file === "string") } : {}),
|
|
1836
|
+
}) !== null) : [];
|
|
1837
|
+
return {
|
|
1838
|
+
offline: true, mode: "enforce", requiresConfirmation: rules.length > 0,
|
|
1839
|
+
items: rules.map((rule) => ({ content: String(rule.content), enforcement: "required" })),
|
|
1840
|
+
conflictSummaries: ["Lore is offline; this action was checked against its verified, unexpired saved policy."],
|
|
1841
|
+
confirmCommand: guardEmergencyCommand(String(request.sessionId), String(request.repo ?? "")),
|
|
1842
|
+
};
|
|
1843
|
+
}
|
|
1844
|
+
catch {
|
|
1845
|
+
if (!knownEnforce)
|
|
1846
|
+
return null;
|
|
1847
|
+
return {
|
|
1848
|
+
offline: true, mode: "enforce", requiresConfirmation: true, items: [],
|
|
1849
|
+
conflictSummaries: ["Lore cannot verify the current enforcement policy. Restore the connection or use an audited emergency override."],
|
|
1850
|
+
confirmCommand: guardEmergencyCommand(String(request.sessionId), String(request.repo ?? "")),
|
|
1851
|
+
};
|
|
1852
|
+
}
|
|
1853
|
+
}
|
|
1854
|
+
async function handleGuardEvent(input, agent, config, sessionId, options, now) {
|
|
1765
1855
|
const toolName = stringField(input.tool_name);
|
|
1766
1856
|
if (toolName === undefined) {
|
|
1767
1857
|
return undefined;
|
|
@@ -1772,7 +1862,11 @@ async function handleGuardEvent(input, agent, config, sessionId, options, now) {
|
|
|
1772
1862
|
}
|
|
1773
1863
|
const store = await runtimeStore(config, options.home);
|
|
1774
1864
|
const state = await readGuardState(agent, sessionId, options.home);
|
|
1775
|
-
|
|
1865
|
+
const rememberedPolicy = await store.readState("guard-policy-state");
|
|
1866
|
+
const enforcementMayHaveChanged = isObject(rememberedPolicy) && (rememberedPolicy.mode === "enforce" ||
|
|
1867
|
+
(Array.isArray(rememberedPolicy.overrides) && rememberedPolicy.overrides.some((entry) => isObject(entry) && entry.mode === "enforce")));
|
|
1868
|
+
if (!enforcementMayHaveChanged &&
|
|
1869
|
+
state.mode === "off" &&
|
|
1776
1870
|
state.modeCheckedAt !== undefined &&
|
|
1777
1871
|
now.getTime() - Date.parse(state.modeCheckedAt) < GUARD_MODE_TTL_MS) {
|
|
1778
1872
|
await recordLocalGuardMetric(store, { outcome: "reused_decision" }, now).catch(() => undefined);
|
|
@@ -1792,8 +1886,7 @@ async function handleGuardEvent(input, agent, config, sessionId, options, now) {
|
|
|
1792
1886
|
? normalizedRepositoryPath(file)
|
|
1793
1887
|
: normalizedRepositoryPath(relativePath);
|
|
1794
1888
|
})
|
|
1795
|
-
.filter((file) => file !== "")
|
|
1796
|
-
.slice(0, 100);
|
|
1889
|
+
.filter((file) => file !== "");
|
|
1797
1890
|
const key = sha256([
|
|
1798
1891
|
"guard",
|
|
1799
1892
|
trigger.action,
|
|
@@ -1802,50 +1895,64 @@ async function handleGuardEvent(input, agent, config, sessionId, options, now) {
|
|
|
1802
1895
|
].join("\0"));
|
|
1803
1896
|
const cachedAt = state.keys[key];
|
|
1804
1897
|
if (cachedAt !== undefined &&
|
|
1898
|
+
!enforcementMayHaveChanged &&
|
|
1899
|
+
state.mode !== "enforce" &&
|
|
1805
1900
|
now.getTime() - Date.parse(cachedAt) < GUARD_KEY_COOLDOWN_MS) {
|
|
1806
1901
|
await recordLocalGuardMetric(store, { outcome: "reused_decision" }, now).catch(() => undefined);
|
|
1807
1902
|
return undefined;
|
|
1808
1903
|
}
|
|
1809
1904
|
const startedAt = Date.now();
|
|
1810
1905
|
let result;
|
|
1906
|
+
const guardRequest = {
|
|
1907
|
+
connector: "lore-cli",
|
|
1908
|
+
agent,
|
|
1909
|
+
sessionId,
|
|
1910
|
+
action: trigger.action,
|
|
1911
|
+
tool: toolName,
|
|
1912
|
+
...(scope?.repo === undefined ? {} : { repo: scope.repo }),
|
|
1913
|
+
...(scope?.path === undefined ? {} : { path: scope.path }),
|
|
1914
|
+
...(files.length === 0 ? {} : { files }),
|
|
1915
|
+
...(trigger.command === undefined
|
|
1916
|
+
? {}
|
|
1917
|
+
: { command: redactSecrets(trigger.command) }),
|
|
1918
|
+
};
|
|
1811
1919
|
try {
|
|
1812
|
-
result = await postGuardCheck(config,
|
|
1813
|
-
connector: "lore-cli",
|
|
1814
|
-
agent,
|
|
1815
|
-
sessionId,
|
|
1816
|
-
action: trigger.action,
|
|
1817
|
-
tool: toolName,
|
|
1818
|
-
...(scope?.repo === undefined ? {} : { repo: scope.repo }),
|
|
1819
|
-
...(scope?.path === undefined ? {} : { path: scope.path }),
|
|
1820
|
-
...(files.length === 0 ? {} : { files }),
|
|
1821
|
-
...(trigger.command === undefined
|
|
1822
|
-
? {}
|
|
1823
|
-
: { command: redactSecrets(trigger.command).slice(0, 10_000) }),
|
|
1824
|
-
}, options.fetch ?? globalThis.fetch, store);
|
|
1920
|
+
result = await postGuardCheck(config, guardRequest, options.fetch ?? globalThis.fetch, store);
|
|
1825
1921
|
}
|
|
1826
1922
|
catch (error) {
|
|
1827
1923
|
await recordLocalGuardMetric(store, { outcome: "failed" }, now).catch(() => undefined);
|
|
1828
|
-
|
|
1924
|
+
result = await offlineGuard(config, store, guardRequest, now, state.mode === "enforce", isContextTransportFailure(error));
|
|
1925
|
+
if (result === null)
|
|
1926
|
+
throw error;
|
|
1829
1927
|
}
|
|
1830
1928
|
if (result === null) {
|
|
1831
1929
|
await recordLocalGuardMetric(store, { outcome: "failed" }, now).catch(() => undefined);
|
|
1832
1930
|
throw new IncompatibleReliabilityError("Lore Guard response is invalid");
|
|
1833
1931
|
}
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
|
|
1837
|
-
|
|
1932
|
+
if (!result.offline)
|
|
1933
|
+
await recordLocalGuardMetric(store, {
|
|
1934
|
+
outcome: "live_check",
|
|
1935
|
+
durationMs: Math.max(0, Date.now() - startedAt),
|
|
1936
|
+
}, now).catch(() => undefined);
|
|
1838
1937
|
state.mode = result.mode;
|
|
1839
1938
|
state.modeCheckedAt = now.toISOString();
|
|
1840
1939
|
if (!result.requiresConfirmation) {
|
|
1841
1940
|
state.keys[key] = now.toISOString();
|
|
1842
1941
|
}
|
|
1843
|
-
await writeGuardState(agent, sessionId, state, options.home);
|
|
1942
|
+
await writeGuardState(agent, sessionId, state, options.home).catch(() => undefined);
|
|
1844
1943
|
if (result.mode === "off") {
|
|
1845
1944
|
return undefined;
|
|
1846
1945
|
}
|
|
1847
1946
|
if (result.requiresConfirmation) {
|
|
1848
1947
|
const reason = guardConfirmationReason(result);
|
|
1948
|
+
if (agent === "polytoken")
|
|
1949
|
+
return { outcome: "deny", reason };
|
|
1950
|
+
if (agent === "copilot-cli") {
|
|
1951
|
+
return {
|
|
1952
|
+
permissionDecision: result.offline || result.policyUnavailable ? "deny" : "ask",
|
|
1953
|
+
permissionDecisionReason: reason,
|
|
1954
|
+
};
|
|
1955
|
+
}
|
|
1849
1956
|
if (agent === "cursor") {
|
|
1850
1957
|
// Cursor's "ask" verdict is unenforced upstream; deny is the only
|
|
1851
1958
|
// reliable gate. The message carries the approve-and-retry path.
|
|
@@ -1858,7 +1965,8 @@ async function handleGuardEvent(input, agent, config, sessionId, options, now) {
|
|
|
1858
1965
|
return {
|
|
1859
1966
|
hookSpecificOutput: {
|
|
1860
1967
|
hookEventName: "PreToolUse",
|
|
1861
|
-
|
|
1968
|
+
// Codex parses `ask` as unsupported and then continues the tool call.
|
|
1969
|
+
permissionDecision: agent === "codex" || result.offline || result.policyUnavailable ? "deny" : "ask",
|
|
1862
1970
|
permissionDecisionReason: reason,
|
|
1863
1971
|
},
|
|
1864
1972
|
};
|
|
@@ -1866,6 +1974,13 @@ async function handleGuardEvent(input, agent, config, sessionId, options, now) {
|
|
|
1866
1974
|
if (result.items.length === 0 && result.conflictSummaries.length === 0) {
|
|
1867
1975
|
return undefined;
|
|
1868
1976
|
}
|
|
1977
|
+
if (agent === "copilot-cli") {
|
|
1978
|
+
// Copilot CLI's preToolUse output has no context-injection field. Prompt
|
|
1979
|
+
// context was already added by userPromptTransformed.
|
|
1980
|
+
return undefined;
|
|
1981
|
+
}
|
|
1982
|
+
if (agent === "polytoken")
|
|
1983
|
+
return { outcome: "allow" };
|
|
1869
1984
|
if (agent === "cursor") {
|
|
1870
1985
|
// Cursor cannot inject agent context at the shell boundary on allow.
|
|
1871
1986
|
return undefined;
|
|
@@ -1895,6 +2010,20 @@ export async function handleHookEvent(value, agent, options = {}) {
|
|
|
1895
2010
|
return undefined;
|
|
1896
2011
|
}
|
|
1897
2012
|
const now = (options.now ?? (() => new Date()))();
|
|
2013
|
+
if (agent === "cursor" && eventName === "SessionStart") {
|
|
2014
|
+
const cwd = stringField(input.cwd);
|
|
2015
|
+
if (!cwd)
|
|
2016
|
+
return { additional_context: "Lore needs a single repository workspace to provide shared context." };
|
|
2017
|
+
const store = await runtimeStore(config, options.home);
|
|
2018
|
+
await store.writeState(cursorSessionKey(sessionId), { sessionId, cwd, updatedAt: now.toISOString() });
|
|
2019
|
+
return { additional_context: [
|
|
2020
|
+
"Lore shared context is connected through the lore-native MCP server.",
|
|
2021
|
+
`For this conversation use sessionId ${JSON.stringify(sessionId)}.`,
|
|
2022
|
+
"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 and records durable teachings through Lore's normal capture pipeline.",
|
|
2023
|
+
"Use the returned context when relevant. If retrieval fails, report that limitation; do not invent shared knowledge.",
|
|
2024
|
+
"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.",
|
|
2025
|
+
].join("\n") };
|
|
2026
|
+
}
|
|
1898
2027
|
if (eventName === "Stop" || eventName === "AssistantResponse") {
|
|
1899
2028
|
await savePending(input, agent, sessionId, now, options.home);
|
|
1900
2029
|
return undefined;
|
|
@@ -1910,6 +2039,20 @@ export async function handleHookEvent(value, agent, options = {}) {
|
|
|
1910
2039
|
return await handleGuardEvent(input, agent, config, sessionId, options, now);
|
|
1911
2040
|
}
|
|
1912
2041
|
catch {
|
|
2042
|
+
const prior = await readGuardState(agent, sessionId, options.home).catch(() => null);
|
|
2043
|
+
const saved = await runtimeStore(config, options.home).then((store) => store.readState("guard-policy-state")).catch(() => null);
|
|
2044
|
+
const scope = await repositoryScope(stringField(input.cwd)).catch(() => undefined);
|
|
2045
|
+
const override = isObject(saved) && Array.isArray(saved.overrides) ? saved.overrides.find((entry) => isObject(entry) && entry.repo === scope?.repo) : undefined;
|
|
2046
|
+
if (prior?.mode === "enforce" || (isObject(override) ? override.mode : isObject(saved) ? saved.mode : undefined) === "enforce") {
|
|
2047
|
+
const reason = "Lore Guard cannot verify its enforcement policy. Restore Lore before retrying.";
|
|
2048
|
+
if (agent === "cursor")
|
|
2049
|
+
return { permission: "deny", userMessage: reason, agentMessage: reason };
|
|
2050
|
+
if (agent === "copilot-cli")
|
|
2051
|
+
return { permissionDecision: "deny", permissionDecisionReason: reason };
|
|
2052
|
+
if (agent === "polytoken")
|
|
2053
|
+
return { outcome: "deny", reason };
|
|
2054
|
+
return { hookSpecificOutput: { hookEventName: "PreToolUse", permissionDecision: "deny", permissionDecisionReason: reason } };
|
|
2055
|
+
}
|
|
1913
2056
|
const warning = "Lore Guard is unavailable; this action is proceeding without a live policy decision.";
|
|
1914
2057
|
if (agent === "cursor") {
|
|
1915
2058
|
return {
|
|
@@ -1917,6 +2060,9 @@ export async function handleHookEvent(value, agent, options = {}) {
|
|
|
1917
2060
|
agentMessage: warning,
|
|
1918
2061
|
};
|
|
1919
2062
|
}
|
|
2063
|
+
if (agent === "copilot-cli" || agent === "polytoken") {
|
|
2064
|
+
return undefined;
|
|
2065
|
+
}
|
|
1920
2066
|
return {
|
|
1921
2067
|
hookSpecificOutput: {
|
|
1922
2068
|
hookEventName: "PreToolUse",
|
|
@@ -1925,6 +2071,55 @@ export async function handleHookEvent(value, agent, options = {}) {
|
|
|
1925
2071
|
};
|
|
1926
2072
|
}
|
|
1927
2073
|
}
|
|
2074
|
+
if (agent === "cursor") {
|
|
2075
|
+
const store = await runtimeStore(config, options.home);
|
|
2076
|
+
const existing = await store.readState(cursorSessionKey(sessionId));
|
|
2077
|
+
const cwd = stringField(input.cwd);
|
|
2078
|
+
if (cwd)
|
|
2079
|
+
await store.writeState(cursorSessionKey(sessionId), {
|
|
2080
|
+
...(isObject(existing) ? existing : {}), sessionId, cwd,
|
|
2081
|
+
...(stringField(input.prompt) ? { prompt: redactSecrets(String(input.prompt)) } : {}),
|
|
2082
|
+
...(stringField(input.prompt_id) ? { promptId: input.prompt_id } : {}), updatedAt: now.toISOString(),
|
|
2083
|
+
});
|
|
2084
|
+
// Cursor's prompt hook supports submission control only. The MCP call
|
|
2085
|
+
// delivers the context; capture here retains native editor event identity.
|
|
2086
|
+
await handlePromptEvent(input, agent, sessionId, config, options, now);
|
|
2087
|
+
return { continue: true };
|
|
2088
|
+
}
|
|
2089
|
+
return handlePromptEvent(input, agent, sessionId, config, options, now);
|
|
2090
|
+
}
|
|
2091
|
+
function cursorSessionKey(sessionId) {
|
|
2092
|
+
return `cursor-session-${sha256(sessionId)}`;
|
|
2093
|
+
}
|
|
2094
|
+
/** MCP requests are bound to a session/workspace observed by the native hook. */
|
|
2095
|
+
async function cursorSession(sessionId, options) {
|
|
2096
|
+
const config = await readRuntimeConfig(options.home);
|
|
2097
|
+
if (!config?.agents.includes("cursor"))
|
|
2098
|
+
throw new Error("Reconnect Cursor to Lore before requesting context.");
|
|
2099
|
+
const store = await runtimeStore(config, options.home);
|
|
2100
|
+
const state = await store.readState(cursorSessionKey(sessionId));
|
|
2101
|
+
if (!isObject(state) || state.sessionId !== sessionId || !stringField(state.cwd)) {
|
|
2102
|
+
throw new Error("Start a new Cursor conversation to register this Lore session.");
|
|
2103
|
+
}
|
|
2104
|
+
return { config, store, state, cwd: stringField(state.cwd) };
|
|
2105
|
+
}
|
|
2106
|
+
export async function getCursorContext(sessionId, prompt, options = {}) {
|
|
2107
|
+
const { config, state, cwd } = await cursorSession(sessionId, options);
|
|
2108
|
+
const now = (options.now ?? (() => new Date()))();
|
|
2109
|
+
const result = await handlePromptEvent({ session_id: sessionId, cwd,
|
|
2110
|
+
prompt: stringField(state.prompt) ?? prompt,
|
|
2111
|
+
...(stringField(state.promptId) ? { prompt_id: state.promptId } : {}),
|
|
2112
|
+
}, "cursor", sessionId, config, options, now, stringField(state.prompt) === undefined);
|
|
2113
|
+
return result?.additional_context || "No relevant shared knowledge was found for this request.";
|
|
2114
|
+
}
|
|
2115
|
+
export async function completeCursorResponse(sessionId, response, options = {}) {
|
|
2116
|
+
const { store, state, cwd } = await cursorSession(sessionId, options);
|
|
2117
|
+
const now = (options.now ?? (() => new Date()))();
|
|
2118
|
+
await savePending({ cwd, last_assistant_message: response }, "cursor", sessionId, now, options.home);
|
|
2119
|
+
const { prompt: _prompt, promptId: _promptId, ...rest } = state;
|
|
2120
|
+
await store.writeState(cursorSessionKey(sessionId), { ...rest, updatedAt: now.toISOString() });
|
|
2121
|
+
}
|
|
2122
|
+
async function handlePromptEvent(input, agent, sessionId, config, options, now, capture = true) {
|
|
1928
2123
|
const fetchImplementation = options.fetch ?? globalThis.fetch;
|
|
1929
2124
|
const prompt = stringField(input.prompt);
|
|
1930
2125
|
if (prompt === undefined) {
|
|
@@ -1945,14 +2140,12 @@ export async function handleHookEvent(value, agent, options = {}) {
|
|
|
1945
2140
|
return agent === "polytoken"
|
|
1946
2141
|
? { outcome: "accept", additional_context: notice }
|
|
1947
2142
|
: agent === "cursor"
|
|
1948
|
-
? {
|
|
1949
|
-
|
|
1950
|
-
|
|
1951
|
-
|
|
1952
|
-
|
|
1953
|
-
}
|
|
1954
|
-
}
|
|
1955
|
-
: { systemMessage: notice };
|
|
2143
|
+
? { additional_context: notice }
|
|
2144
|
+
: agent === "copilot-cli"
|
|
2145
|
+
? {
|
|
2146
|
+
modifiedTransformedPrompt: `${notice}\n\n${stringField(input.transformed_prompt) ?? prompt}`,
|
|
2147
|
+
}
|
|
2148
|
+
: { systemMessage: notice };
|
|
1956
2149
|
}
|
|
1957
2150
|
const noticeForFlush = (result) => {
|
|
1958
2151
|
if (result?.state === "auth-blocked") {
|
|
@@ -1968,7 +2161,7 @@ export async function handleHookEvent(value, agent, options = {}) {
|
|
|
1968
2161
|
catch {
|
|
1969
2162
|
notices.push("Lore retained queued captures locally, but replay could not run. Run lore doctor.");
|
|
1970
2163
|
}
|
|
1971
|
-
const pending = await readPending(agent, sessionId, options.home);
|
|
2164
|
+
const pending = capture ? await readPending(agent, sessionId, options.home) : null;
|
|
1972
2165
|
const lineage = await readLineageMetadata(options.home, options.environment ?? process.env);
|
|
1973
2166
|
let delivery = emptyDelivery();
|
|
1974
2167
|
if (pending !== null) {
|
|
@@ -2042,8 +2235,10 @@ export async function handleHookEvent(value, agent, options = {}) {
|
|
|
2042
2235
|
}
|
|
2043
2236
|
let enqueued = false;
|
|
2044
2237
|
try {
|
|
2045
|
-
|
|
2046
|
-
|
|
2238
|
+
if (capture) {
|
|
2239
|
+
await enqueueCapture(store, { kind: "prompt", request: observation });
|
|
2240
|
+
enqueued = true;
|
|
2241
|
+
}
|
|
2047
2242
|
}
|
|
2048
2243
|
catch (error) {
|
|
2049
2244
|
notices.push(error instanceof ReliabilityStoreError &&
|
|
@@ -2124,8 +2319,20 @@ export async function handleHookEvent(value, agent, options = {}) {
|
|
|
2124
2319
|
additional_context: injectedContext,
|
|
2125
2320
|
};
|
|
2126
2321
|
}
|
|
2322
|
+
if (agent === "cursor")
|
|
2323
|
+
return { additional_context: injectedContext };
|
|
2324
|
+
if (agent === "copilot-cli") {
|
|
2325
|
+
const copilotContext = [delivery.context, receipt, reliabilityNotice]
|
|
2326
|
+
.filter((entry) => entry !== undefined && entry !== "")
|
|
2327
|
+
.join("\n\n");
|
|
2328
|
+
if (copilotContext === "") {
|
|
2329
|
+
return undefined;
|
|
2330
|
+
}
|
|
2331
|
+
return {
|
|
2332
|
+
modifiedTransformedPrompt: `${copilotContext}\n\n${stringField(input.transformed_prompt) ?? prompt}`,
|
|
2333
|
+
};
|
|
2334
|
+
}
|
|
2127
2335
|
return {
|
|
2128
|
-
...(agent === "cursor" ? { continue: true } : {}),
|
|
2129
2336
|
...(systemMessage === undefined ? {} : { systemMessage }),
|
|
2130
2337
|
...(injectedContext === ""
|
|
2131
2338
|
? {}
|
|
@@ -2154,7 +2361,9 @@ function parseAgent(args) {
|
|
|
2154
2361
|
return isCommandHookAgent(value) ? value : null;
|
|
2155
2362
|
}
|
|
2156
2363
|
function nativeIntegrationId(agent) {
|
|
2157
|
-
return
|
|
2364
|
+
return agent === "copilot-vscode"
|
|
2365
|
+
? "preview/copilot-vscode"
|
|
2366
|
+
: `native/${agent}`;
|
|
2158
2367
|
}
|
|
2159
2368
|
async function beginHookInvocation(agent) {
|
|
2160
2369
|
const config = await readRuntimeConfig();
|