@lore-co/cli 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +110 -0
- package/README.md +218 -0
- package/dist/cli.d.ts +28 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +792 -0
- package/dist/cli.js.map +1 -0
- package/dist/devin-client.d.ts +63 -0
- package/dist/devin-client.d.ts.map +1 -0
- package/dist/devin-client.js +231 -0
- package/dist/devin-client.js.map +1 -0
- package/dist/devin.d.ts +2 -0
- package/dist/devin.d.ts.map +1 -0
- package/dist/devin.js +479 -0
- package/dist/devin.js.map +1 -0
- package/dist/generated-assets.d.ts +7 -0
- package/dist/generated-assets.d.ts.map +1 -0
- package/dist/generated-assets.js +8 -0
- package/dist/generated-assets.js.map +1 -0
- package/dist/github.d.ts +13 -0
- package/dist/github.d.ts.map +1 -0
- package/dist/github.js +559 -0
- package/dist/github.js.map +1 -0
- package/dist/host.d.ts +24 -0
- package/dist/host.d.ts.map +1 -0
- package/dist/host.js +337 -0
- package/dist/host.js.map +1 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +8 -0
- package/dist/index.js.map +1 -0
- package/dist/main.d.ts +2 -0
- package/dist/main.d.ts.map +1 -0
- package/dist/main.js +10 -0
- package/dist/main.js.map +1 -0
- package/dist/repository.d.ts +3 -0
- package/dist/repository.d.ts.map +1 -0
- package/dist/repository.js +60 -0
- package/dist/repository.js.map +1 -0
- package/dist/review-output.d.ts +59 -0
- package/dist/review-output.d.ts.map +1 -0
- package/dist/review-output.js +112 -0
- package/dist/review-output.js.map +1 -0
- package/dist/runtime.d.ts +63 -0
- package/dist/runtime.d.ts.map +1 -0
- package/dist/runtime.js +612 -0
- package/dist/runtime.js.map +1 -0
- package/dist/update.d.ts +2 -0
- package/dist/update.d.ts.map +1 -0
- package/dist/update.js +97 -0
- package/dist/update.js.map +1 -0
- package/dist/version.d.ts +3 -0
- package/dist/version.d.ts.map +1 -0
- package/dist/version.js +6 -0
- package/dist/version.js.map +1 -0
- package/package.json +47 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"review-output.js","sourceRoot":"","sources":["../src/review-output.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,oBAAoB,GAAG;IAClC,IAAI,EAAE,QAAQ;IACd,oBAAoB,EAAE,KAAK;IAC3B,QAAQ,EAAE,CAAC,SAAS,EAAE,UAAU,CAAC;IACjC,UAAU,EAAE;QACV,OAAO,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,SAAS,EAAE,CAAC,EAAE,OAAO,EAAE,KAAK,EAAE;QACzD,QAAQ,EAAE;YACR,IAAI,EAAE,OAAO;YACb,KAAK,EAAE;gBACL,IAAI,EAAE,QAAQ;gBACd,oBAAoB,EAAE,KAAK;gBAC3B,QAAQ,EAAE,CAAC,UAAU,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC;gBACvD,UAAU,EAAE;oBACV,QAAQ,EAAE,EAAE,IAAI,EAAE,CAAC,UAAU,EAAE,MAAM,EAAE,QAAQ,EAAE,KAAK,CAAC,EAAE;oBACzD,KAAK,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,SAAS,EAAE,CAAC,EAAE,OAAO,EAAE,KAAK,EAAE;oBACvD,IAAI,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,SAAS,EAAE,CAAC,EAAE,OAAO,EAAE,KAAK,EAAE;oBACtD,IAAI,EAAE;wBACJ,IAAI,EAAE,CAAC,QAAQ,EAAE,MAAM,CAAC;wBACxB,SAAS,EAAE,CAAC;wBACZ,OAAO,EAAE,KAAK;qBACf;oBACD,IAAI,EAAE,EAAE,IAAI,EAAE,CAAC,SAAS,EAAE,MAAM,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE;iBAChD;aACF;SACF;KACF;CACO,CAAC;AAiBX,MAAM,iBAAiB,GAAG,IAAI,GAAG,CAAiB;IAChD,UAAU;IACV,MAAM;IACN,QAAQ;IACR,KAAK;CACN,CAAC,CAAC;AAEH,SAAS,QAAQ,CAAC,KAAc;IAC9B,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;AAC9E,CAAC;AAED,SAAS,cAAc,CACrB,KAA8B,EAC9B,QAA2B;IAE3B,MAAM,IAAI,GAAG,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IAChC,OAAO,CACL,IAAI,CAAC,MAAM,KAAK,QAAQ,CAAC,MAAM;QAC/B,QAAQ,CAAC,KAAK,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,MAAM,CAAC,MAAM,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC,CACnD,CAAC;AACJ,CAAC;AAED,SAAS,cAAc,CAAC,KAAc;IACpC,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,CAAC,IAAI,EAAE,KAAK,EAAE,CAAC;AAC1D,CAAC;AAED,SAAS,OAAO,CAAC,MAAc;IAC7B,MAAM,IAAI,KAAK,CAAC,qDAAqD,MAAM,EAAE,CAAC,CAAC;AACjF,CAAC;AAED,MAAM,UAAU,oBAAoB,CAAC,KAAc;IACjD,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,CAAC,cAAc,CAAC,KAAK,EAAE,CAAC,SAAS,EAAE,UAAU,CAAC,CAAC,EAAE,CAAC;QACxE,OAAO,CAAC,oCAAoC,CAAC,CAAC;IAChD,CAAC;IACD,IAAI,CAAC,cAAc,CAAC,KAAK,CAAC,OAAO,CAAC,EAAE,CAAC;QACnC,OAAO,CAAC,oCAAoC,CAAC,CAAC;IAChD,CAAC;IACD,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,QAAQ,CAAC,EAAE,CAAC;QACnC,OAAO,CAAC,2BAA2B,CAAC,CAAC;IACvC,CAAC;IAED,MAAM,QAAQ,GAAG,KAAK,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,OAAO,EAAE,KAAK,EAAiB,EAAE;QACpE,IACE,CAAC,QAAQ,CAAC,OAAO,CAAC;YAClB,CAAC,cAAc,CAAC,OAAO,EAAE;gBACvB,UAAU;gBACV,OAAO;gBACP,MAAM;gBACN,MAAM;gBACN,MAAM;aACP,CAAC,EACF,CAAC;YACD,OAAO,CAAC,YAAY,KAAK,wCAAwC,CAAC,CAAC;QACrE,CAAC;QACD,IACE,OAAO,OAAO,CAAC,QAAQ,KAAK,QAAQ;YACpC,CAAC,iBAAiB,CAAC,GAAG,CAAC,OAAO,CAAC,QAA0B,CAAC,EAC1D,CAAC;YACD,OAAO,CAAC,YAAY,KAAK,uBAAuB,CAAC,CAAC;QACpD,CAAC;QACD,IAAI,CAAC,cAAc,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;YACnC,OAAO,CAAC,YAAY,KAAK,oCAAoC,CAAC,CAAC;QACjE,CAAC;QACD,IAAI,CAAC,cAAc,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC;YAClC,OAAO,CAAC,YAAY,KAAK,mCAAmC,CAAC,CAAC;QAChE,CAAC;QACD,IACE,OAAO,CAAC,IAAI,KAAK,IAAI;YACrB,CAAC,cAAc,CAAC,OAAO,CAAC,IAAI,CAAC,EAC7B,CAAC;YACD,OAAO,CAAC,YAAY,KAAK,2CAA2C,CAAC,CAAC;QACxE,CAAC;QACD,IACE,OAAO,CAAC,IAAI,KAAK,IAAI;YACrB,CAAC,OAAO,OAAO,CAAC,IAAI,KAAK,QAAQ;gBAC/B,CAAC,MAAM,CAAC,SAAS,CAAC,OAAO,CAAC,IAAI,CAAC;gBAC/B,OAAO,CAAC,IAAI,GAAG,CAAC,CAAC,EACnB,CAAC;YACD,OAAO,CAAC,YAAY,KAAK,2CAA2C,CAAC,CAAC;QACxE,CAAC;QACD,OAAO;YACL,QAAQ,EAAE,OAAO,CAAC,QAA0B;YAC5C,KAAK,EAAE,OAAO,CAAC,KAAK;YACpB,IAAI,EAAE,OAAO,CAAC,IAAI;YAClB,IAAI,EAAE,OAAO,CAAC,IAAI;YAClB,IAAI,EAAE,OAAO,CAAC,IAAI;SACnB,CAAC;IACJ,CAAC,CAAC,CAAC;IAEH,OAAO,EAAE,OAAO,EAAE,KAAK,CAAC,OAAO,EAAE,QAAQ,EAAE,CAAC;AAC9C,CAAC;AAED,MAAM,UAAU,iBAAiB,CAAC,GAAW;IAC3C,IAAI,GAAG,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC;QACtB,MAAM,IAAI,KAAK,CAAC,wBAAwB,CAAC,CAAC;IAC5C,CAAC;IACD,IAAI,MAAe,CAAC;IACpB,IAAI,CAAC;QACH,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IAC3B,CAAC;IAAC,MAAM,CAAC;QACP,MAAM,IAAI,KAAK,CAAC,kCAAkC,CAAC,CAAC;IACtD,CAAC;IACD,OAAO,oBAAoB,CAAC,MAAM,CAAC,CAAC;AACtC,CAAC"}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
export type AgentName = "codex" | "claude";
|
|
2
|
+
interface PendingAssistantMessage {
|
|
3
|
+
agent: AgentName;
|
|
4
|
+
sessionId: string;
|
|
5
|
+
assistantMessage: string;
|
|
6
|
+
cwd?: string;
|
|
7
|
+
turnId?: string;
|
|
8
|
+
capturedAt: string;
|
|
9
|
+
}
|
|
10
|
+
export interface TurnRequest {
|
|
11
|
+
idempotencyKey: string;
|
|
12
|
+
connector: "lore-cli";
|
|
13
|
+
eventId: string;
|
|
14
|
+
agent: AgentName;
|
|
15
|
+
sessionId: string;
|
|
16
|
+
previousAssistant: {
|
|
17
|
+
content: string;
|
|
18
|
+
id?: string;
|
|
19
|
+
timestamp?: string;
|
|
20
|
+
};
|
|
21
|
+
currentUser: {
|
|
22
|
+
content: string;
|
|
23
|
+
id?: string;
|
|
24
|
+
timestamp?: string;
|
|
25
|
+
};
|
|
26
|
+
occurredAt: string;
|
|
27
|
+
scope?: {
|
|
28
|
+
repo?: string;
|
|
29
|
+
path?: string;
|
|
30
|
+
};
|
|
31
|
+
learningScope: Record<string, never>;
|
|
32
|
+
metadata?: {
|
|
33
|
+
cwd?: string;
|
|
34
|
+
previousTurnId?: string;
|
|
35
|
+
promptId?: string;
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
interface HookInput {
|
|
39
|
+
session_id?: unknown;
|
|
40
|
+
hook_event_name?: unknown;
|
|
41
|
+
cwd?: unknown;
|
|
42
|
+
turn_id?: unknown;
|
|
43
|
+
prompt_id?: unknown;
|
|
44
|
+
prompt?: unknown;
|
|
45
|
+
last_assistant_message?: unknown;
|
|
46
|
+
}
|
|
47
|
+
export interface HookRuntimeOptions {
|
|
48
|
+
home?: string;
|
|
49
|
+
fetch?: typeof fetch;
|
|
50
|
+
now?: () => Date;
|
|
51
|
+
}
|
|
52
|
+
export interface HookResult {
|
|
53
|
+
hookSpecificOutput: {
|
|
54
|
+
hookEventName: "UserPromptSubmit";
|
|
55
|
+
additionalContext: string;
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
export declare function redactSecrets(text: string): string;
|
|
59
|
+
export declare function createTurnRequest(input: HookInput, agent: AgentName, pending: PendingAssistantMessage, now: Date): Promise<TurnRequest | null>;
|
|
60
|
+
export declare function handleHookEvent(value: unknown, agent: AgentName, options?: HookRuntimeOptions): Promise<HookResult | undefined>;
|
|
61
|
+
export declare function runHook(args?: readonly string[]): Promise<void>;
|
|
62
|
+
export {};
|
|
63
|
+
//# sourceMappingURL=runtime.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"runtime.d.ts","sourceRoot":"","sources":["../src/runtime.ts"],"names":[],"mappings":"AA4BA,MAAM,MAAM,SAAS,GAAG,OAAO,GAAG,QAAQ,CAAC;AAU3C,UAAU,uBAAuB;IAC/B,KAAK,EAAE,SAAS,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,gBAAgB,EAAE,MAAM,CAAC;IACzB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,WAAW;IAC1B,cAAc,EAAE,MAAM,CAAC;IACvB,SAAS,EAAE,UAAU,CAAC;IACtB,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,SAAS,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,iBAAiB,EAAE;QACjB,OAAO,EAAE,MAAM,CAAC;QAChB,EAAE,CAAC,EAAE,MAAM,CAAC;QACZ,SAAS,CAAC,EAAE,MAAM,CAAC;KACpB,CAAC;IACF,WAAW,EAAE;QACX,OAAO,EAAE,MAAM,CAAC;QAChB,EAAE,CAAC,EAAE,MAAM,CAAC;QACZ,SAAS,CAAC,EAAE,MAAM,CAAC;KACpB,CAAC;IACF,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE;QACN,IAAI,CAAC,EAAE,MAAM,CAAC;QACd,IAAI,CAAC,EAAE,MAAM,CAAC;KACf,CAAC;IACF,aAAa,EAAE,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;IACrC,QAAQ,CAAC,EAAE;QACT,GAAG,CAAC,EAAE,MAAM,CAAC;QACb,cAAc,CAAC,EAAE,MAAM,CAAC;QACxB,QAAQ,CAAC,EAAE,MAAM,CAAC;KACnB,CAAC;CACH;AAuBD,UAAU,SAAS;IACjB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,GAAG,CAAC,EAAE,OAAO,CAAC;IACd,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,sBAAsB,CAAC,EAAE,OAAO,CAAC;CAClC;AAED,MAAM,WAAW,kBAAkB;IACjC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,OAAO,KAAK,CAAC;IACrB,GAAG,CAAC,EAAE,MAAM,IAAI,CAAC;CAClB;AAED,MAAM,WAAW,UAAU;IACzB,kBAAkB,EAAE;QAClB,aAAa,EAAE,kBAAkB,CAAC;QAClC,iBAAiB,EAAE,MAAM,CAAC;KAC3B,CAAC;CACH;AA2CD,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAwBlD;AA6JD,wBAAsB,iBAAiB,CACrC,KAAK,EAAE,SAAS,EAChB,KAAK,EAAE,SAAS,EAChB,OAAO,EAAE,uBAAuB,EAChC,GAAG,EAAE,IAAI,GACR,OAAO,CAAC,WAAW,GAAG,IAAI,CAAC,CA0D7B;AA8TD,wBAAsB,eAAe,CACnC,KAAK,EAAE,OAAO,EACd,KAAK,EAAE,SAAS,EAChB,OAAO,GAAE,kBAAuB,GAC/B,OAAO,CAAC,UAAU,GAAG,SAAS,CAAC,CA4GjC;AAoBD,wBAAsB,OAAO,CAC3B,IAAI,GAAE,SAAS,MAAM,EAA0B,GAC9C,OAAO,CAAC,IAAI,CAAC,CAaf"}
|
package/dist/runtime.js
ADDED
|
@@ -0,0 +1,612 @@
|
|
|
1
|
+
import { createHash, randomUUID } from "node:crypto";
|
|
2
|
+
import { access, chmod, mkdir, readFile, readdir, rename, rm, stat, writeFile, } from "node:fs/promises";
|
|
3
|
+
import { constants as fsConstants } from "node:fs";
|
|
4
|
+
import { dirname, parse, relative, resolve } from "node:path";
|
|
5
|
+
import { homedir } from "node:os";
|
|
6
|
+
import { pathToFileURL } from "node:url";
|
|
7
|
+
import { repositoryScopeFromGitRoot } from "./repository.js";
|
|
8
|
+
const RUNTIME_VERSION = typeof __LORE_VERSION__ === "string" && __LORE_VERSION__ !== ""
|
|
9
|
+
? __LORE_VERSION__
|
|
10
|
+
: "0.1.0";
|
|
11
|
+
const IS_STANDALONE_RUNTIME = typeof __LORE_STANDALONE__ === "boolean" && __LORE_STANDALONE__;
|
|
12
|
+
const MAX_STDIN_BYTES = 1024 * 1024;
|
|
13
|
+
const MAX_CONTEXT_CHARS = 12_000;
|
|
14
|
+
const PENDING_ASSISTANT_MAX_AGE_MS = 24 * 60 * 60 * 1_000;
|
|
15
|
+
const MAX_QUEUE_ITEMS = 100;
|
|
16
|
+
function homeDirectory(override) {
|
|
17
|
+
return override ?? process.env.HOME ?? homedir();
|
|
18
|
+
}
|
|
19
|
+
function loreDirectory(home) {
|
|
20
|
+
return resolve(homeDirectory(home), ".lore");
|
|
21
|
+
}
|
|
22
|
+
function isObject(value) {
|
|
23
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
24
|
+
}
|
|
25
|
+
function stringField(value) {
|
|
26
|
+
return typeof value === "string" && value.trim() !== ""
|
|
27
|
+
? value
|
|
28
|
+
: undefined;
|
|
29
|
+
}
|
|
30
|
+
function sha256(value) {
|
|
31
|
+
return createHash("sha256").update(value).digest("hex");
|
|
32
|
+
}
|
|
33
|
+
function deterministicUuid(value) {
|
|
34
|
+
const hex = sha256(value).split("");
|
|
35
|
+
hex[12] = "5";
|
|
36
|
+
hex[16] = ((Number.parseInt(hex[16] ?? "0", 16) & 0x3) | 0x8).toString(16);
|
|
37
|
+
const joined = hex.join("");
|
|
38
|
+
return [
|
|
39
|
+
joined.slice(0, 8),
|
|
40
|
+
joined.slice(8, 12),
|
|
41
|
+
joined.slice(12, 16),
|
|
42
|
+
joined.slice(16, 20),
|
|
43
|
+
joined.slice(20, 32),
|
|
44
|
+
].join("-");
|
|
45
|
+
}
|
|
46
|
+
export function redactSecrets(text) {
|
|
47
|
+
return text
|
|
48
|
+
.replace(/-----BEGIN (?:RSA |EC |OPENSSH )?PRIVATE KEY-----[\s\S]*?-----END (?:RSA |EC |OPENSSH )?PRIVATE KEY-----/gu, "[REDACTED_PRIVATE_KEY]")
|
|
49
|
+
.replace(/\bsk-[A-Za-z0-9_-]{16,}\b/gu, "[REDACTED_API_KEY]")
|
|
50
|
+
.replace(/\b(?:github_pat_[A-Za-z0-9_]{16,}|gh[oprsu]_[A-Za-z0-9]{16,})\b/gu, "[REDACTED_GITHUB_TOKEN]")
|
|
51
|
+
.replace(/\bxox[baprs]-[A-Za-z0-9-]{10,}\b/gu, "[REDACTED_SLACK_TOKEN]")
|
|
52
|
+
.replace(/\bAKIA[A-Z0-9]{16}\b/gu, "[REDACTED_AWS_KEY]")
|
|
53
|
+
.replace(/((?:api[_-]?key|access[_-]?token|auth[_-]?token|password|passwd|secret)\s*[:=]\s*)(["']?)[^\s"',;]{8,}\2/giu, "$1[REDACTED]")
|
|
54
|
+
.replace(/(authorization\s*:\s*bearer\s+)[A-Za-z0-9._~+/=-]{8,}/giu, "$1[REDACTED]");
|
|
55
|
+
}
|
|
56
|
+
async function atomicWriteJson(path, value, mode = 0o600) {
|
|
57
|
+
await mkdir(dirname(path), { recursive: true, mode: 0o700 });
|
|
58
|
+
const temporaryPath = `${path}.${process.pid}.${randomUUID()}.tmp`;
|
|
59
|
+
await writeFile(temporaryPath, `${JSON.stringify(value, null, 2)}\n`, {
|
|
60
|
+
encoding: "utf8",
|
|
61
|
+
mode,
|
|
62
|
+
flag: "wx",
|
|
63
|
+
});
|
|
64
|
+
await rename(temporaryPath, path);
|
|
65
|
+
await chmod(path, mode);
|
|
66
|
+
}
|
|
67
|
+
async function readRuntimeConfig(home) {
|
|
68
|
+
try {
|
|
69
|
+
const parsed = JSON.parse(await readFile(resolve(loreDirectory(home), "config.json"), "utf8"));
|
|
70
|
+
if (!isObject(parsed) ||
|
|
71
|
+
parsed.version !== 1 ||
|
|
72
|
+
typeof parsed.apiUrl !== "string" ||
|
|
73
|
+
typeof parsed.token !== "string" ||
|
|
74
|
+
!Array.isArray(parsed.agents)) {
|
|
75
|
+
return null;
|
|
76
|
+
}
|
|
77
|
+
const agents = parsed.agents.filter((agent) => agent === "codex" || agent === "claude");
|
|
78
|
+
const timeoutMs = typeof parsed.timeoutMs === "number" &&
|
|
79
|
+
Number.isInteger(parsed.timeoutMs) &&
|
|
80
|
+
parsed.timeoutMs >= 250 &&
|
|
81
|
+
parsed.timeoutMs <= 10_000
|
|
82
|
+
? parsed.timeoutMs
|
|
83
|
+
: undefined;
|
|
84
|
+
return {
|
|
85
|
+
version: 1,
|
|
86
|
+
apiUrl: parsed.apiUrl,
|
|
87
|
+
token: parsed.token,
|
|
88
|
+
agents,
|
|
89
|
+
...(timeoutMs === undefined ? {} : { timeoutMs }),
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
catch {
|
|
93
|
+
return null;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
function pendingPath(agent, sessionId, home) {
|
|
97
|
+
return resolve(loreDirectory(home), "state", "pending", `${sha256(`${agent}\0${sessionId}`)}.json`);
|
|
98
|
+
}
|
|
99
|
+
async function savePending(input, agent, sessionId, now, home) {
|
|
100
|
+
const assistantMessage = stringField(input.last_assistant_message);
|
|
101
|
+
if (assistantMessage === undefined) {
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
const cwd = stringField(input.cwd);
|
|
105
|
+
const turnId = stringField(input.turn_id);
|
|
106
|
+
await atomicWriteJson(pendingPath(agent, sessionId, home), {
|
|
107
|
+
agent,
|
|
108
|
+
sessionId,
|
|
109
|
+
assistantMessage: redactSecrets(assistantMessage),
|
|
110
|
+
...(cwd === undefined ? {} : { cwd }),
|
|
111
|
+
...(turnId === undefined ? {} : { turnId }),
|
|
112
|
+
capturedAt: now.toISOString(),
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
async function consumePending(agent, sessionId, home) {
|
|
116
|
+
const path = pendingPath(agent, sessionId, home);
|
|
117
|
+
try {
|
|
118
|
+
const parsed = JSON.parse(await readFile(path, "utf8"));
|
|
119
|
+
await rm(path, { force: true });
|
|
120
|
+
if (!isObject(parsed) ||
|
|
121
|
+
parsed.agent !== agent ||
|
|
122
|
+
parsed.sessionId !== sessionId ||
|
|
123
|
+
typeof parsed.assistantMessage !== "string" ||
|
|
124
|
+
typeof parsed.capturedAt !== "string") {
|
|
125
|
+
return null;
|
|
126
|
+
}
|
|
127
|
+
const capturedAt = Date.parse(parsed.capturedAt);
|
|
128
|
+
if (!Number.isFinite(capturedAt) ||
|
|
129
|
+
Date.now() - capturedAt > PENDING_ASSISTANT_MAX_AGE_MS) {
|
|
130
|
+
return null;
|
|
131
|
+
}
|
|
132
|
+
const cwd = stringField(parsed.cwd);
|
|
133
|
+
const turnId = stringField(parsed.turnId);
|
|
134
|
+
return {
|
|
135
|
+
agent,
|
|
136
|
+
sessionId,
|
|
137
|
+
assistantMessage: parsed.assistantMessage,
|
|
138
|
+
...(cwd === undefined ? {} : { cwd }),
|
|
139
|
+
...(turnId === undefined ? {} : { turnId }),
|
|
140
|
+
capturedAt: parsed.capturedAt,
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
catch {
|
|
144
|
+
return null;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
async function repositoryScope(cwd) {
|
|
148
|
+
if (cwd === undefined) {
|
|
149
|
+
return undefined;
|
|
150
|
+
}
|
|
151
|
+
let current = resolve(cwd);
|
|
152
|
+
const root = parse(current).root;
|
|
153
|
+
while (true) {
|
|
154
|
+
try {
|
|
155
|
+
await access(resolve(current, ".git"), fsConstants.F_OK);
|
|
156
|
+
const scopedPath = relative(current, resolve(cwd));
|
|
157
|
+
return {
|
|
158
|
+
repo: await repositoryScopeFromGitRoot(current),
|
|
159
|
+
...(scopedPath === "" ? {} : { path: scopedPath }),
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
catch {
|
|
163
|
+
if (current === root) {
|
|
164
|
+
return {
|
|
165
|
+
repo: await repositoryScopeFromGitRoot(resolve(cwd)),
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
current = dirname(current);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
export async function createTurnRequest(input, agent, pending, now) {
|
|
173
|
+
const prompt = stringField(input.prompt);
|
|
174
|
+
if (prompt === undefined) {
|
|
175
|
+
return null;
|
|
176
|
+
}
|
|
177
|
+
const promptId = stringField(input.prompt_id) ?? stringField(input.turn_id);
|
|
178
|
+
const cwd = stringField(input.cwd) ?? pending.cwd;
|
|
179
|
+
const previousAssistantMessage = redactSecrets(pending.assistantMessage);
|
|
180
|
+
const userPrompt = redactSecrets(prompt);
|
|
181
|
+
const idempotencyKey = sha256(JSON.stringify({
|
|
182
|
+
agent,
|
|
183
|
+
sessionId: pending.sessionId,
|
|
184
|
+
previousTurnId: pending.turnId,
|
|
185
|
+
promptId,
|
|
186
|
+
previousAssistantMessage,
|
|
187
|
+
userPrompt,
|
|
188
|
+
}));
|
|
189
|
+
const scope = await repositoryScope(cwd);
|
|
190
|
+
return {
|
|
191
|
+
idempotencyKey,
|
|
192
|
+
connector: "lore-cli",
|
|
193
|
+
eventId: pending.turnId === undefined && promptId === undefined
|
|
194
|
+
? deterministicUuid(`lore-turn\0${idempotencyKey}`)
|
|
195
|
+
: deterministicUuid(`lore-turn\0${agent}\0${pending.sessionId}\0${pending.turnId ?? ""}\0${promptId ?? ""}`),
|
|
196
|
+
agent,
|
|
197
|
+
sessionId: pending.sessionId,
|
|
198
|
+
previousAssistant: {
|
|
199
|
+
content: previousAssistantMessage,
|
|
200
|
+
...(pending.turnId === undefined ? {} : { id: pending.turnId }),
|
|
201
|
+
timestamp: pending.capturedAt,
|
|
202
|
+
},
|
|
203
|
+
currentUser: {
|
|
204
|
+
content: userPrompt,
|
|
205
|
+
...(promptId === undefined ? {} : { id: promptId }),
|
|
206
|
+
timestamp: now.toISOString(),
|
|
207
|
+
},
|
|
208
|
+
occurredAt: now.toISOString(),
|
|
209
|
+
...(scope === undefined ? {} : { scope }),
|
|
210
|
+
learningScope: {},
|
|
211
|
+
...(cwd === undefined &&
|
|
212
|
+
pending.turnId === undefined &&
|
|
213
|
+
promptId === undefined
|
|
214
|
+
? {}
|
|
215
|
+
: {
|
|
216
|
+
metadata: {
|
|
217
|
+
...(cwd === undefined ? {} : { cwd }),
|
|
218
|
+
...(pending.turnId === undefined
|
|
219
|
+
? {}
|
|
220
|
+
: { previousTurnId: pending.turnId }),
|
|
221
|
+
...(promptId === undefined ? {} : { promptId }),
|
|
222
|
+
},
|
|
223
|
+
}),
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
function createPromptObservationRequest(input, agent, sessionId, now, scope) {
|
|
227
|
+
const prompt = stringField(input.prompt);
|
|
228
|
+
if (prompt === undefined) {
|
|
229
|
+
return null;
|
|
230
|
+
}
|
|
231
|
+
const promptId = stringField(input.prompt_id) ?? stringField(input.turn_id);
|
|
232
|
+
const cwd = stringField(input.cwd);
|
|
233
|
+
const idempotencyKey = sha256(JSON.stringify({
|
|
234
|
+
agent,
|
|
235
|
+
sessionId,
|
|
236
|
+
promptId,
|
|
237
|
+
prompt: redactSecrets(prompt),
|
|
238
|
+
}));
|
|
239
|
+
return {
|
|
240
|
+
idempotencyKey,
|
|
241
|
+
connector: "lore-cli",
|
|
242
|
+
agent,
|
|
243
|
+
sessionId,
|
|
244
|
+
eventId: deterministicUuid(`lore-prompt\0${agent}\0${sessionId}\0${promptId ?? "first"}`),
|
|
245
|
+
prompt: redactSecrets(prompt),
|
|
246
|
+
...(promptId === undefined ? {} : { promptId }),
|
|
247
|
+
timestamp: now.toISOString(),
|
|
248
|
+
...(scope === undefined ? {} : { scope }),
|
|
249
|
+
learningScope: {},
|
|
250
|
+
...(cwd === undefined && promptId === undefined
|
|
251
|
+
? {}
|
|
252
|
+
: {
|
|
253
|
+
metadata: {
|
|
254
|
+
...(cwd === undefined ? {} : { cwd }),
|
|
255
|
+
...(promptId === undefined ? {} : { promptId }),
|
|
256
|
+
},
|
|
257
|
+
}),
|
|
258
|
+
};
|
|
259
|
+
}
|
|
260
|
+
function turnsUrl(apiUrl) {
|
|
261
|
+
return `${apiUrl.replace(/\/+$/u, "")}/v1/turns`;
|
|
262
|
+
}
|
|
263
|
+
function observationsUrl(apiUrl) {
|
|
264
|
+
return `${apiUrl.replace(/\/+$/u, "")}/v1/observations`;
|
|
265
|
+
}
|
|
266
|
+
function contextDeliveriesUrl(apiUrl) {
|
|
267
|
+
return `${apiUrl.replace(/\/+$/u, "")}/v1/context/deliveries`;
|
|
268
|
+
}
|
|
269
|
+
function boundedContext(value) {
|
|
270
|
+
return Array.from(value.trim()).slice(0, MAX_CONTEXT_CHARS).join("");
|
|
271
|
+
}
|
|
272
|
+
function contextFromResponse(value) {
|
|
273
|
+
if (!isObject(value)) {
|
|
274
|
+
return "";
|
|
275
|
+
}
|
|
276
|
+
for (const candidate of [value.context, value.additionalContext]) {
|
|
277
|
+
if (typeof candidate === "string" && candidate.trim() !== "") {
|
|
278
|
+
return boundedContext(candidate);
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
if (isObject(value.hookSpecificOutput)) {
|
|
282
|
+
const candidate = value.hookSpecificOutput.additionalContext;
|
|
283
|
+
if (typeof candidate === "string" && candidate.trim() !== "") {
|
|
284
|
+
return boundedContext(candidate);
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
if (isObject(value.context)) {
|
|
288
|
+
const candidate = value.context.text;
|
|
289
|
+
if (typeof candidate === "string" && candidate.trim() !== "") {
|
|
290
|
+
return boundedContext(candidate);
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
return "";
|
|
294
|
+
}
|
|
295
|
+
async function postTurn(config, request, fetchImplementation) {
|
|
296
|
+
const { idempotencyKey, ...body } = request;
|
|
297
|
+
const response = await fetchImplementation(turnsUrl(config.apiUrl), {
|
|
298
|
+
method: "POST",
|
|
299
|
+
headers: {
|
|
300
|
+
authorization: `Bearer ${config.token}`,
|
|
301
|
+
"content-type": "application/json",
|
|
302
|
+
"idempotency-key": idempotencyKey,
|
|
303
|
+
"user-agent": `lore-cli/${RUNTIME_VERSION}`,
|
|
304
|
+
},
|
|
305
|
+
body: JSON.stringify(body),
|
|
306
|
+
signal: AbortSignal.timeout(config.timeoutMs ?? 2_500),
|
|
307
|
+
});
|
|
308
|
+
if (!response.ok) {
|
|
309
|
+
throw new Error(`Lore turn request failed with HTTP ${response.status}`);
|
|
310
|
+
}
|
|
311
|
+
const text = await response.text();
|
|
312
|
+
if (text.trim() === "") {
|
|
313
|
+
return "";
|
|
314
|
+
}
|
|
315
|
+
return contextFromResponse(JSON.parse(text));
|
|
316
|
+
}
|
|
317
|
+
async function postPromptObservation(config, request, fetchImplementation) {
|
|
318
|
+
const response = await fetchImplementation(observationsUrl(config.apiUrl), {
|
|
319
|
+
method: "POST",
|
|
320
|
+
headers: {
|
|
321
|
+
authorization: `Bearer ${config.token}`,
|
|
322
|
+
"content-type": "application/json",
|
|
323
|
+
"idempotency-key": request.idempotencyKey,
|
|
324
|
+
"user-agent": `lore-cli/${RUNTIME_VERSION}`,
|
|
325
|
+
},
|
|
326
|
+
body: JSON.stringify({
|
|
327
|
+
connector: request.connector ?? "lore-cli",
|
|
328
|
+
eventId: request.eventId,
|
|
329
|
+
agent: request.agent,
|
|
330
|
+
sessionId: request.sessionId,
|
|
331
|
+
...(request.scope === undefined ? {} : { scope: request.scope }),
|
|
332
|
+
learningScope: request.learningScope,
|
|
333
|
+
...(request.prompt === "" ? {} : { task: request.prompt }),
|
|
334
|
+
messages: [
|
|
335
|
+
{
|
|
336
|
+
role: "user",
|
|
337
|
+
content: request.prompt,
|
|
338
|
+
...(request.promptId === undefined ? {} : { id: request.promptId }),
|
|
339
|
+
timestamp: request.timestamp,
|
|
340
|
+
},
|
|
341
|
+
],
|
|
342
|
+
occurredAt: request.timestamp,
|
|
343
|
+
...(request.metadata === undefined
|
|
344
|
+
? {}
|
|
345
|
+
: { metadata: request.metadata }),
|
|
346
|
+
}),
|
|
347
|
+
signal: AbortSignal.timeout(config.timeoutMs ?? 2_500),
|
|
348
|
+
});
|
|
349
|
+
if (!response.ok) {
|
|
350
|
+
throw new Error(`Lore prompt observation failed with HTTP ${response.status}`);
|
|
351
|
+
}
|
|
352
|
+
await response.body?.cancel();
|
|
353
|
+
}
|
|
354
|
+
async function getPromptContext(config, agent, sessionId, sourceEventId, prompt, scope, fetchImplementation) {
|
|
355
|
+
const response = await fetchImplementation(contextDeliveriesUrl(config.apiUrl), {
|
|
356
|
+
method: "POST",
|
|
357
|
+
headers: {
|
|
358
|
+
authorization: `Bearer ${config.token}`,
|
|
359
|
+
"content-type": "application/json",
|
|
360
|
+
"user-agent": `lore-cli/${RUNTIME_VERSION}`,
|
|
361
|
+
},
|
|
362
|
+
body: JSON.stringify({
|
|
363
|
+
connector: "lore-cli",
|
|
364
|
+
eventId: deterministicUuid(`lore-context\0${sourceEventId}`),
|
|
365
|
+
sessionId,
|
|
366
|
+
task: {
|
|
367
|
+
agent,
|
|
368
|
+
task: redactSecrets(prompt),
|
|
369
|
+
...(scope === undefined ? {} : { scope }),
|
|
370
|
+
},
|
|
371
|
+
}),
|
|
372
|
+
signal: AbortSignal.timeout(config.timeoutMs ?? 2_500),
|
|
373
|
+
});
|
|
374
|
+
if (!response.ok) {
|
|
375
|
+
throw new Error(`Lore context request failed with HTTP ${response.status}`);
|
|
376
|
+
}
|
|
377
|
+
const text = await response.text();
|
|
378
|
+
if (text.trim() === "") {
|
|
379
|
+
return "";
|
|
380
|
+
}
|
|
381
|
+
return contextFromResponse(JSON.parse(text));
|
|
382
|
+
}
|
|
383
|
+
function queueDirectory(home) {
|
|
384
|
+
return resolve(loreDirectory(home), "queue");
|
|
385
|
+
}
|
|
386
|
+
async function trimQueue(home) {
|
|
387
|
+
const directory = queueDirectory(home);
|
|
388
|
+
let entries;
|
|
389
|
+
try {
|
|
390
|
+
entries = (await readdir(directory))
|
|
391
|
+
.filter((entry) => entry.endsWith(".json"))
|
|
392
|
+
.sort();
|
|
393
|
+
}
|
|
394
|
+
catch {
|
|
395
|
+
return;
|
|
396
|
+
}
|
|
397
|
+
const excess = entries.length - MAX_QUEUE_ITEMS + 1;
|
|
398
|
+
if (excess <= 0) {
|
|
399
|
+
return;
|
|
400
|
+
}
|
|
401
|
+
await Promise.all(entries
|
|
402
|
+
.slice(0, excess)
|
|
403
|
+
.map(async (entry) => rm(resolve(directory, entry), { force: true })));
|
|
404
|
+
}
|
|
405
|
+
async function enqueue(request, home) {
|
|
406
|
+
await trimQueue(home);
|
|
407
|
+
const path = resolve(queueDirectory(home), `${request.request.idempotencyKey}.json`);
|
|
408
|
+
await atomicWriteJson(path, request);
|
|
409
|
+
}
|
|
410
|
+
function isTurnRequest(value) {
|
|
411
|
+
return (isObject(value) &&
|
|
412
|
+
typeof value.idempotencyKey === "string" &&
|
|
413
|
+
value.connector === "lore-cli" &&
|
|
414
|
+
typeof value.eventId === "string" &&
|
|
415
|
+
(value.agent === "codex" || value.agent === "claude") &&
|
|
416
|
+
typeof value.sessionId === "string" &&
|
|
417
|
+
isObject(value.previousAssistant) &&
|
|
418
|
+
typeof value.previousAssistant.content === "string" &&
|
|
419
|
+
isObject(value.currentUser) &&
|
|
420
|
+
typeof value.currentUser.content === "string" &&
|
|
421
|
+
typeof value.occurredAt === "string");
|
|
422
|
+
}
|
|
423
|
+
function isPromptObservationRequest(value) {
|
|
424
|
+
return (isObject(value) &&
|
|
425
|
+
typeof value.idempotencyKey === "string" &&
|
|
426
|
+
(value.agent === "codex" || value.agent === "claude") &&
|
|
427
|
+
typeof value.sessionId === "string" &&
|
|
428
|
+
typeof value.eventId === "string" &&
|
|
429
|
+
typeof value.prompt === "string" &&
|
|
430
|
+
typeof value.timestamp === "string");
|
|
431
|
+
}
|
|
432
|
+
function queuedRequest(value) {
|
|
433
|
+
if (isTurnRequest(value)) {
|
|
434
|
+
return { kind: "turn", request: value };
|
|
435
|
+
}
|
|
436
|
+
if (!isObject(value) || !isObject(value.request)) {
|
|
437
|
+
return null;
|
|
438
|
+
}
|
|
439
|
+
if (value.kind === "turn" && isTurnRequest(value.request)) {
|
|
440
|
+
return { kind: "turn", request: value.request };
|
|
441
|
+
}
|
|
442
|
+
if (value.kind === "prompt" &&
|
|
443
|
+
isPromptObservationRequest(value.request)) {
|
|
444
|
+
return { kind: "prompt", request: value.request };
|
|
445
|
+
}
|
|
446
|
+
return null;
|
|
447
|
+
}
|
|
448
|
+
async function retryOne(config, fetchImplementation, home) {
|
|
449
|
+
const directory = queueDirectory(home);
|
|
450
|
+
let first;
|
|
451
|
+
try {
|
|
452
|
+
first = (await readdir(directory))
|
|
453
|
+
.filter((entry) => entry.endsWith(".json"))
|
|
454
|
+
.sort()[0];
|
|
455
|
+
}
|
|
456
|
+
catch {
|
|
457
|
+
return;
|
|
458
|
+
}
|
|
459
|
+
if (first === undefined) {
|
|
460
|
+
return;
|
|
461
|
+
}
|
|
462
|
+
const path = resolve(directory, first);
|
|
463
|
+
try {
|
|
464
|
+
const parsed = JSON.parse(await readFile(path, "utf8"));
|
|
465
|
+
const queued = queuedRequest(parsed);
|
|
466
|
+
if (queued === null) {
|
|
467
|
+
await rm(path, { force: true });
|
|
468
|
+
return;
|
|
469
|
+
}
|
|
470
|
+
if (queued.kind === "turn") {
|
|
471
|
+
await postTurn(config, queued.request, fetchImplementation);
|
|
472
|
+
}
|
|
473
|
+
else {
|
|
474
|
+
await postPromptObservation(config, queued.request, fetchImplementation);
|
|
475
|
+
}
|
|
476
|
+
await rm(path, { force: true });
|
|
477
|
+
}
|
|
478
|
+
catch {
|
|
479
|
+
// Retry on a later prompt. Hook failures are deliberately invisible.
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
export async function handleHookEvent(value, agent, options = {}) {
|
|
483
|
+
if (!isObject(value)) {
|
|
484
|
+
return undefined;
|
|
485
|
+
}
|
|
486
|
+
const input = value;
|
|
487
|
+
const eventName = stringField(input.hook_event_name);
|
|
488
|
+
const sessionId = stringField(input.session_id);
|
|
489
|
+
if (eventName === undefined || sessionId === undefined) {
|
|
490
|
+
return undefined;
|
|
491
|
+
}
|
|
492
|
+
const config = await readRuntimeConfig(options.home);
|
|
493
|
+
if (config === null || !config.agents.includes(agent)) {
|
|
494
|
+
return undefined;
|
|
495
|
+
}
|
|
496
|
+
const now = (options.now ?? (() => new Date()))();
|
|
497
|
+
if (eventName === "Stop") {
|
|
498
|
+
await savePending(input, agent, sessionId, now, options.home);
|
|
499
|
+
return undefined;
|
|
500
|
+
}
|
|
501
|
+
if (eventName === "SessionEnd") {
|
|
502
|
+
// Non-interactive Claude and Codex invocations emit SessionEnd even when
|
|
503
|
+
// their persisted session can be resumed. Keep the bounded pending turn so
|
|
504
|
+
// the next invocation can still capture a correction.
|
|
505
|
+
return undefined;
|
|
506
|
+
}
|
|
507
|
+
if (eventName !== "UserPromptSubmit") {
|
|
508
|
+
return undefined;
|
|
509
|
+
}
|
|
510
|
+
const fetchImplementation = options.fetch ?? globalThis.fetch;
|
|
511
|
+
await retryOne(config, fetchImplementation, options.home);
|
|
512
|
+
const pending = await consumePending(agent, sessionId, options.home);
|
|
513
|
+
const prompt = stringField(input.prompt);
|
|
514
|
+
if (prompt === undefined) {
|
|
515
|
+
return undefined;
|
|
516
|
+
}
|
|
517
|
+
let context = "";
|
|
518
|
+
if (pending !== null) {
|
|
519
|
+
const request = await createTurnRequest(input, agent, pending, now);
|
|
520
|
+
if (request === null) {
|
|
521
|
+
return undefined;
|
|
522
|
+
}
|
|
523
|
+
try {
|
|
524
|
+
context = await postTurn(config, request, fetchImplementation);
|
|
525
|
+
}
|
|
526
|
+
catch {
|
|
527
|
+
try {
|
|
528
|
+
await enqueue({ kind: "turn", request }, options.home);
|
|
529
|
+
}
|
|
530
|
+
catch {
|
|
531
|
+
// The connector must fail open even when its local queue is unavailable.
|
|
532
|
+
}
|
|
533
|
+
try {
|
|
534
|
+
context = await getPromptContext(config, agent, sessionId, request.eventId, prompt, request.scope, fetchImplementation);
|
|
535
|
+
}
|
|
536
|
+
catch {
|
|
537
|
+
return undefined;
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
else {
|
|
542
|
+
const scope = await repositoryScope(stringField(input.cwd));
|
|
543
|
+
const observation = createPromptObservationRequest(input, agent, sessionId, now, scope);
|
|
544
|
+
if (observation === null) {
|
|
545
|
+
return undefined;
|
|
546
|
+
}
|
|
547
|
+
try {
|
|
548
|
+
await postPromptObservation(config, observation, fetchImplementation);
|
|
549
|
+
}
|
|
550
|
+
catch {
|
|
551
|
+
try {
|
|
552
|
+
await enqueue({ kind: "prompt", request: observation }, options.home);
|
|
553
|
+
}
|
|
554
|
+
catch {
|
|
555
|
+
// The connector must fail open even when its local queue is unavailable.
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
try {
|
|
559
|
+
context = await getPromptContext(config, agent, sessionId, observation.eventId, prompt, scope, fetchImplementation);
|
|
560
|
+
}
|
|
561
|
+
catch {
|
|
562
|
+
return undefined;
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
if (context === "") {
|
|
566
|
+
return undefined;
|
|
567
|
+
}
|
|
568
|
+
return {
|
|
569
|
+
hookSpecificOutput: {
|
|
570
|
+
hookEventName: "UserPromptSubmit",
|
|
571
|
+
additionalContext: context,
|
|
572
|
+
},
|
|
573
|
+
};
|
|
574
|
+
}
|
|
575
|
+
async function readStdin() {
|
|
576
|
+
process.stdin.setEncoding("utf8");
|
|
577
|
+
let input = "";
|
|
578
|
+
for await (const chunk of process.stdin) {
|
|
579
|
+
input += String(chunk);
|
|
580
|
+
if (Buffer.byteLength(input, "utf8") > MAX_STDIN_BYTES) {
|
|
581
|
+
throw new Error("Hook input is too large");
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
return JSON.parse(input);
|
|
585
|
+
}
|
|
586
|
+
function parseAgent(args) {
|
|
587
|
+
const index = args.indexOf("--agent");
|
|
588
|
+
const value = index < 0 ? undefined : args[index + 1];
|
|
589
|
+
return value === "codex" || value === "claude" ? value : null;
|
|
590
|
+
}
|
|
591
|
+
export async function runHook(args = process.argv.slice(2)) {
|
|
592
|
+
const agent = parseAgent(args);
|
|
593
|
+
if (agent === null) {
|
|
594
|
+
return;
|
|
595
|
+
}
|
|
596
|
+
try {
|
|
597
|
+
const result = await handleHookEvent(await readStdin(), agent);
|
|
598
|
+
if (result !== undefined) {
|
|
599
|
+
process.stdout.write(`${JSON.stringify(result)}\n`);
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
catch {
|
|
603
|
+
// Native hooks must never block or add error noise to an agent session.
|
|
604
|
+
}
|
|
605
|
+
}
|
|
606
|
+
const entryPath = process.argv[1];
|
|
607
|
+
if (!IS_STANDALONE_RUNTIME &&
|
|
608
|
+
entryPath !== undefined &&
|
|
609
|
+
import.meta.url === pathToFileURL(resolve(entryPath)).href) {
|
|
610
|
+
void runHook();
|
|
611
|
+
}
|
|
612
|
+
//# sourceMappingURL=runtime.js.map
|