@open-cr-agent/cli 0.5.0 → 0.6.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/dist/cloud/account-salt.d.ts +6 -0
- package/dist/cloud/account-salt.js +33 -0
- package/dist/{review → cloud}/account-settings.js +5 -4
- package/dist/cloud/browser.d.ts +17 -0
- package/dist/cloud/browser.js +37 -0
- package/dist/cloud/client.d.ts +93 -0
- package/dist/cloud/client.js +221 -0
- package/dist/cloud/credentials.d.ts +18 -0
- package/dist/cloud/credentials.js +52 -0
- package/dist/cloud/deps.d.ts +14 -0
- package/dist/cloud/deps.js +34 -0
- package/dist/cloud/file-lock.d.ts +14 -0
- package/dist/cloud/file-lock.js +77 -0
- package/dist/cloud/findings.d.ts +8 -0
- package/dist/{review/cloud-findings.js → cloud/findings.js} +10 -32
- package/dist/cloud/gateway-token.d.ts +18 -0
- package/dist/cloud/gateway-token.js +63 -0
- package/dist/cloud/memory.d.ts +8 -0
- package/dist/cloud/memory.js +68 -0
- package/dist/cloud/private-file.d.ts +8 -0
- package/dist/cloud/private-file.js +46 -0
- package/dist/{review/cloud-providers.d.ts → cloud/providers.d.ts} +12 -6
- package/dist/cloud/providers.js +132 -0
- package/dist/cloud/review.d.ts +28 -0
- package/dist/cloud/review.js +76 -0
- package/dist/cloud/settings.d.ts +22 -0
- package/dist/cloud/settings.js +64 -0
- package/dist/cloud/upload.d.ts +20 -0
- package/dist/{review/cloud-upload.js → cloud/upload.js} +74 -71
- package/dist/commands/login.d.ts +5 -0
- package/dist/commands/login.js +164 -0
- package/dist/commands/memory.d.ts +3 -0
- package/dist/{memory.js → commands/memory.js} +21 -20
- package/dist/commands/metrics.d.ts +45 -0
- package/dist/{metrics.js → commands/metrics.js} +32 -45
- package/dist/{plugins-command.d.ts → commands/plugins.d.ts} +3 -9
- package/dist/{plugins-command.js → commands/plugins.js} +29 -53
- package/dist/{review → commands/review}/args.d.ts +7 -10
- package/dist/{review → commands/review}/args.js +11 -16
- package/dist/commands/review/deliver.d.ts +7 -0
- package/dist/commands/review/deliver.js +56 -0
- package/dist/commands/review/deps.d.ts +18 -0
- package/dist/commands/review/deps.js +2 -0
- package/dist/commands/review/execute-run.d.ts +13 -0
- package/dist/commands/review/execute-run.js +97 -0
- package/dist/commands/review/exit-code.d.ts +4 -0
- package/dist/commands/review/exit-code.js +29 -0
- package/dist/commands/review/plan-prices.d.ts +3 -0
- package/dist/commands/review/plan-prices.js +18 -0
- package/dist/{review → commands/review}/plan-render.js +22 -2
- package/dist/commands/review/plan-run.d.ts +5 -0
- package/dist/commands/review/plan-run.js +38 -0
- package/dist/commands/review/plan-schema.d.ts +97 -0
- package/dist/commands/review/plan-schema.js +34 -0
- package/dist/{review → commands/review}/progress.d.ts +3 -5
- package/dist/{review → commands/review}/progress.js +3 -3
- package/dist/{review → commands/review}/provenance.d.ts +4 -2
- package/dist/{review → commands/review}/provenance.js +2 -3
- package/dist/{review → commands/review}/render.js +1 -1
- package/dist/commands/review/resolve-run.d.ts +29 -0
- package/dist/commands/review/resolve-run.js +125 -0
- package/dist/commands/review/review-options.d.ts +6 -0
- package/dist/commands/review/review-options.js +35 -0
- package/dist/{review → commands/review}/runtimes.js +1 -1
- package/dist/{review → commands/review}/sarif-input.d.ts +0 -1
- package/dist/{review → commands/review}/sarif-input.js +5 -5
- package/dist/commands/review/sarif.d.ts +96 -0
- package/dist/{review → commands/review}/sarif.js +29 -7
- package/dist/commands/review/settings-sources.d.ts +17 -0
- package/dist/commands/review/settings-sources.js +30 -0
- package/dist/commands/review/target.d.ts +38 -0
- package/dist/commands/review/target.js +126 -0
- package/dist/commands/review.d.ts +7 -0
- package/dist/commands/review.js +34 -0
- package/dist/config/cli-config.d.ts +41 -0
- package/dist/config/cli-config.js +199 -0
- package/dist/{review/remote-config.d.ts → config/remote.d.ts} +3 -3
- package/dist/{review/remote-config.js → config/remote.js} +5 -23
- package/dist/{review/config.d.ts → config/schema.d.ts} +8 -31
- package/dist/config/schema.js +138 -0
- package/dist/config/settings.d.ts +37 -0
- package/dist/config/settings.js +133 -0
- package/dist/config/user-dir.d.ts +3 -0
- package/dist/config/user-dir.js +10 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +10 -1
- package/dist/internal.d.ts +6 -0
- package/dist/internal.js +7 -0
- package/dist/io/exit.d.ts +9 -0
- package/dist/io/exit.js +12 -0
- package/dist/io/output.d.ts +4 -0
- package/dist/io/output.js +2 -0
- package/dist/io/usage-error.d.ts +5 -0
- package/dist/io/usage-error.js +8 -0
- package/dist/main.js +2 -2
- package/dist/{review/account-plugins.d.ts → plugins/account.d.ts} +2 -2
- package/dist/{review/account-plugins.js → plugins/account.js} +7 -7
- package/dist/{review/plugins.d.ts → plugins/load.d.ts} +2 -2
- package/dist/{review/plugins.js → plugins/load.js} +6 -5
- package/dist/plugins/npm.d.ts +8 -0
- package/dist/plugins/npm.js +24 -0
- package/dist/{plugin-store.d.ts → plugins/store.d.ts} +1 -1
- package/dist/{plugin-store.js → plugins/store.js} +5 -4
- package/dist/repository-id.d.ts +12 -0
- package/dist/repository-id.js +72 -0
- package/dist/run.d.ts +2 -2
- package/dist/run.js +15 -12
- package/dist/session/store.d.ts +9 -0
- package/dist/session/store.js +31 -0
- package/package.json +17 -7
- package/dist/account-salt.d.ts +0 -7
- package/dist/account-salt.js +0 -43
- package/dist/cloud.d.ts +0 -34
- package/dist/cloud.js +0 -272
- package/dist/memory.d.ts +0 -4
- package/dist/metrics.d.ts +0 -47
- package/dist/review/cloud-findings.d.ts +0 -31
- package/dist/review/cloud-memory.d.ts +0 -6
- package/dist/review/cloud-memory.js +0 -58
- package/dist/review/cloud-providers.js +0 -60
- package/dist/review/cloud-review.d.ts +0 -19
- package/dist/review/cloud-review.js +0 -51
- package/dist/review/cloud-settings.d.ts +0 -16
- package/dist/review/cloud-settings.js +0 -118
- package/dist/review/cloud-upload.d.ts +0 -60
- package/dist/review/command.d.ts +0 -33
- package/dist/review/command.js +0 -293
- package/dist/review/config.js +0 -304
- package/dist/review/sarif.d.ts +0 -4
- package/dist/review/settings-sources.d.ts +0 -11
- package/dist/review/settings-sources.js +0 -58
- package/dist/review/target.d.ts +0 -18
- package/dist/review/target.js +0 -210
- /package/dist/{review → cloud}/account-settings.d.ts +0 -0
- /package/dist/{review → commands/review}/plan-render.d.ts +0 -0
- /package/dist/{review → commands/review}/render.d.ts +0 -0
- /package/dist/{review → commands/review}/runtimes.d.ts +0 -0
- /package/dist/{review → io}/terminal.d.ts +0 -0
- /package/dist/{review → io}/terminal.js +0 -0
|
@@ -1,31 +1,9 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
|
|
7
|
-
/-----BEGIN [A-Z0-9 ]*PRIVATE KEY-----[\s\S]*?(-----END [A-Z0-9 ]*PRIVATE KEY-----|$)/g,
|
|
8
|
-
/\b(AKIA|ASIA)[0-9A-Z]{16}\b/g,
|
|
9
|
-
/\b(ghp|gho|ghs|ghu|ghr)_[A-Za-z0-9]{20,}\b/g,
|
|
10
|
-
/\bgithub_pat_[A-Za-z0-9_]{20,}\b/g,
|
|
11
|
-
/\bglpat-[A-Za-z0-9_-]{20,}\b/g,
|
|
12
|
-
/\bsk-(ant-|or-|proj-)?[A-Za-z0-9_-]{20,}\b/g,
|
|
13
|
-
/\bxox[abprs]-[A-Za-z0-9-]{10,}\b/g,
|
|
14
|
-
/\bAIza[0-9A-Za-z_-]{35}\b/g,
|
|
15
|
-
/\bnpm_[A-Za-z0-9]{36}\b/g,
|
|
16
|
-
];
|
|
17
|
-
// A long run mixing lower and upper case and digits: a likely key. A hex
|
|
18
|
-
// digest (one case) and a path (has a slash) are left alone.
|
|
19
|
-
const LONG_RUN = /\b[A-Za-z0-9+/_-]{32,}={0,2}/g;
|
|
20
|
-
const looksRandom = (s) => /[a-z]/.test(s) && /[A-Z]/.test(s) && /\d/.test(s) && !s.includes("/");
|
|
21
|
-
export const REDACTED = "[redacted]";
|
|
22
|
-
export function redact(text) {
|
|
23
|
-
let out = text;
|
|
24
|
-
for (const pattern of PATTERNS)
|
|
25
|
-
out = out.replace(pattern, REDACTED);
|
|
26
|
-
out = out.replace(LONG_RUN, (m) => (looksRandom(m) ? REDACTED : m));
|
|
27
|
-
return { text: out, redacted: out !== text };
|
|
28
|
-
}
|
|
1
|
+
import { MAX_FIELD, MAX_FINDINGS, MAX_TOTAL_BYTES, redact, } from "@open-cr-agent/cloud-contract";
|
|
2
|
+
// The findings a review sends ocra Cloud when the account shares them
|
|
3
|
+
// (ADR-0028, 2): what the web needs to show the review, after the contract's
|
|
4
|
+
// redaction pass and within its bounds. The server redacts and bounds again;
|
|
5
|
+
// this pass keeps a secret from leaving the machine at all. It catches
|
|
6
|
+
// patterns, not every secret.
|
|
29
7
|
/** The report's findings as uploaded, redacted and bounded; `left` counts those not sent. */
|
|
30
8
|
export function sharedFindings(report) {
|
|
31
9
|
const findings = [];
|
|
@@ -57,9 +35,9 @@ function share(f) {
|
|
|
57
35
|
fingerprint: f.fingerprint,
|
|
58
36
|
reviewer: f.reviewer,
|
|
59
37
|
severity: f.severity,
|
|
60
|
-
category: f.category,
|
|
38
|
+
category: text(f.category),
|
|
61
39
|
verification: f.verification ?? "unchecked",
|
|
62
|
-
file: f.file,
|
|
40
|
+
file: text(f.file),
|
|
63
41
|
lineStart: f.lineRange?.start ?? null,
|
|
64
42
|
lineEnd: f.lineRange?.end ?? null,
|
|
65
43
|
title: text(f.title),
|
|
@@ -75,6 +53,6 @@ function share(f) {
|
|
|
75
53
|
}
|
|
76
54
|
// Bytes as sent, escapes included, so the body stays under the server's limit.
|
|
77
55
|
function textBytes(f) {
|
|
78
|
-
return [f.title, f.body, f.suggestion ?? "", f.code].reduce((sum, s) => sum + Buffer.byteLength(JSON.stringify(s)), 0);
|
|
56
|
+
return [f.title, f.body, f.suggestion ?? "", f.code ?? ""].reduce((sum, s) => sum + Buffer.byteLength(JSON.stringify(s)), 0);
|
|
79
57
|
}
|
|
80
|
-
//# sourceMappingURL=
|
|
58
|
+
//# sourceMappingURL=findings.js.map
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { Credentials } from "./credentials.js";
|
|
2
|
+
import type { CloudDeps } from "./deps.js";
|
|
3
|
+
export declare const RENEW_BEFORE_MS: number;
|
|
4
|
+
export declare class GatewayToken {
|
|
5
|
+
private timer;
|
|
6
|
+
private stopped;
|
|
7
|
+
private failing;
|
|
8
|
+
private credentials;
|
|
9
|
+
private readonly deps;
|
|
10
|
+
private readonly warn;
|
|
11
|
+
constructor(credentials: Credentials, deps: CloudDeps, warn: (message: string) => void);
|
|
12
|
+
get value(): string;
|
|
13
|
+
start(): void;
|
|
14
|
+
stop(): void;
|
|
15
|
+
private schedule;
|
|
16
|
+
private renew;
|
|
17
|
+
}
|
|
18
|
+
//# sourceMappingURL=gateway-token.d.ts.map
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { MAX_TIMER_MS } from "@open-cr-agent/core/internal";
|
|
2
|
+
import { CloudClient, sessionLostReason } from "./client.js";
|
|
3
|
+
// The access token a run's models use at the ocra Cloud gateway lives an
|
|
4
|
+
// hour; a run may last longer. While the run lasts, the token is renewed a
|
|
5
|
+
// few minutes before it expires, for a runtime that reads its key at each
|
|
6
|
+
// call (the direct runtime does).
|
|
7
|
+
export const RENEW_BEFORE_MS = 5 * 60_000;
|
|
8
|
+
const RETRY_MS = 60_000;
|
|
9
|
+
export class GatewayToken {
|
|
10
|
+
timer;
|
|
11
|
+
stopped = false;
|
|
12
|
+
failing = false;
|
|
13
|
+
credentials;
|
|
14
|
+
deps;
|
|
15
|
+
warn;
|
|
16
|
+
constructor(credentials, deps, warn) {
|
|
17
|
+
this.credentials = credentials;
|
|
18
|
+
this.deps = deps;
|
|
19
|
+
this.warn = warn;
|
|
20
|
+
}
|
|
21
|
+
get value() {
|
|
22
|
+
return this.credentials.access_token;
|
|
23
|
+
}
|
|
24
|
+
start() {
|
|
25
|
+
this.schedule(this.credentials.expires_at - RENEW_BEFORE_MS - this.deps.now());
|
|
26
|
+
}
|
|
27
|
+
stop() {
|
|
28
|
+
this.stopped = true;
|
|
29
|
+
clearTimeout(this.timer);
|
|
30
|
+
}
|
|
31
|
+
schedule(delayMs) {
|
|
32
|
+
if (this.stopped)
|
|
33
|
+
return;
|
|
34
|
+
this.timer = setTimeout(() => void this.renew(), Math.min(Math.max(delayMs, 0), MAX_TIMER_MS));
|
|
35
|
+
this.timer.unref?.();
|
|
36
|
+
}
|
|
37
|
+
async renew() {
|
|
38
|
+
let reason;
|
|
39
|
+
try {
|
|
40
|
+
// Asking for more than the token has left renews it.
|
|
41
|
+
const session = await new CloudClient(this.deps).session(2 * RENEW_BEFORE_MS);
|
|
42
|
+
if (this.stopped)
|
|
43
|
+
return;
|
|
44
|
+
if (session.kind === "ok") {
|
|
45
|
+
this.credentials = session.credentials;
|
|
46
|
+
this.failing = false;
|
|
47
|
+
this.start();
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
reason = session.kind === "signed-out" ? "you signed out" : sessionLostReason(session);
|
|
51
|
+
}
|
|
52
|
+
catch (error) {
|
|
53
|
+
reason = error instanceof Error ? error.message : "error";
|
|
54
|
+
}
|
|
55
|
+
if (!this.failing) {
|
|
56
|
+
this.warn(`could not renew the ocra Cloud token (${reason}); model calls through ocra Cloud fail once it expires`);
|
|
57
|
+
this.failing = true;
|
|
58
|
+
}
|
|
59
|
+
if (this.deps.now() < this.credentials.expires_at)
|
|
60
|
+
this.schedule(RETRY_MS);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
//# sourceMappingURL=gateway-token.js.map
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { MemoryEntry } from "@open-cr-agent/core";
|
|
2
|
+
import type { CloudDeps } from "./deps.js";
|
|
3
|
+
/** The account's entries for the repository; [] and a warning when they cannot be read. */
|
|
4
|
+
export declare function fetchAccountMemory(deps: CloudDeps, repoHash: string, warn: (message: string) => void): Promise<MemoryEntry[]>;
|
|
5
|
+
/** Whether the account remembers any finding, for any repository; false when that cannot be read. */
|
|
6
|
+
export declare function accountHasMemory(deps: CloudDeps): Promise<boolean>;
|
|
7
|
+
export declare function parseAccountMemory(list: readonly unknown[], warn: (message: string) => void): MemoryEntry[];
|
|
8
|
+
//# sourceMappingURL=memory.d.ts.map
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { MAX_MEMORY_PER_REPO } from "@open-cr-agent/cloud-contract";
|
|
2
|
+
import { errorMessage } from "@open-cr-agent/core";
|
|
3
|
+
import { memoryEntrySchema } from "@open-cr-agent/core/internal";
|
|
4
|
+
import { CloudClient, sessionLostReason } from "./client.js";
|
|
5
|
+
// The findings the account remembers for one repository (ADR-0028, 4), set
|
|
6
|
+
// from the web. A review applies them with .ocra/memory.json's.
|
|
7
|
+
/** The account's entries for the repository; [] and a warning when they cannot be read. */
|
|
8
|
+
export async function fetchAccountMemory(deps, repoHash, warn) {
|
|
9
|
+
const cannotRead = (reason) => {
|
|
10
|
+
warn(`could not read your ocra Cloud memory (${reason}); the review applies the repository's alone`);
|
|
11
|
+
return [];
|
|
12
|
+
};
|
|
13
|
+
let answer;
|
|
14
|
+
try {
|
|
15
|
+
answer = await new CloudClient(deps).memory(repoHash);
|
|
16
|
+
}
|
|
17
|
+
catch (error) {
|
|
18
|
+
return cannotRead(errorMessage(error));
|
|
19
|
+
}
|
|
20
|
+
switch (answer.kind) {
|
|
21
|
+
case "ok":
|
|
22
|
+
return parseAccountMemory(answer.value, warn);
|
|
23
|
+
case "signed-out":
|
|
24
|
+
return [];
|
|
25
|
+
case "status":
|
|
26
|
+
// A server without account memory has none to apply.
|
|
27
|
+
return answer.status === 404 ? [] : cannotRead(`HTTP ${answer.status}`);
|
|
28
|
+
case "malformed":
|
|
29
|
+
warn("ignoring your ocra Cloud memory: the server's answer has no entries");
|
|
30
|
+
return [];
|
|
31
|
+
default:
|
|
32
|
+
return cannotRead(sessionLostReason(answer));
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
/** Whether the account remembers any finding, for any repository; false when that cannot be read. */
|
|
36
|
+
export async function accountHasMemory(deps) {
|
|
37
|
+
try {
|
|
38
|
+
const answer = await new CloudClient(deps).memory();
|
|
39
|
+
return answer.kind === "ok" && answer.value.length > 0;
|
|
40
|
+
}
|
|
41
|
+
catch {
|
|
42
|
+
return false;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
export function parseAccountMemory(list, warn) {
|
|
46
|
+
const entries = [];
|
|
47
|
+
let refused = 0;
|
|
48
|
+
// The server keeps no more per repository; more is not an answer to trust.
|
|
49
|
+
for (const raw of list.slice(0, MAX_MEMORY_PER_REPO)) {
|
|
50
|
+
const r = (typeof raw === "object" && raw !== null ? raw : {});
|
|
51
|
+
const parsed = memoryEntrySchema.safeParse({
|
|
52
|
+
fingerprint: r.fingerprint,
|
|
53
|
+
file: r.file,
|
|
54
|
+
title: r.title,
|
|
55
|
+
reason: r.reason,
|
|
56
|
+
...(typeof r.createdAt === "string" ? { added: r.createdAt.slice(0, 10) } : {}),
|
|
57
|
+
});
|
|
58
|
+
if (parsed.success)
|
|
59
|
+
entries.push(parsed.data);
|
|
60
|
+
else
|
|
61
|
+
refused += 1;
|
|
62
|
+
}
|
|
63
|
+
if (refused > 0) {
|
|
64
|
+
warn(`ignoring ${refused} ocra Cloud memory entr${refused > 1 ? "ies" : "y"} this version of ocra cannot read`);
|
|
65
|
+
}
|
|
66
|
+
return entries;
|
|
67
|
+
}
|
|
68
|
+
//# sourceMappingURL=memory.js.map
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/** Writes the file atomically, readable only by this user. */
|
|
2
|
+
export declare function writePrivateFile(path: string, text: string): Promise<void>;
|
|
3
|
+
/**
|
|
4
|
+
* Writes the file atomically unless it exists: false when another writer
|
|
5
|
+
* made it first, whose content then stands.
|
|
6
|
+
*/
|
|
7
|
+
export declare function createPrivateFile(path: string, text: string): Promise<boolean>;
|
|
8
|
+
//# sourceMappingURL=private-file.d.ts.map
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { randomBytes } from "node:crypto";
|
|
2
|
+
import { link, mkdir, rename, rm, writeFile } from "node:fs/promises";
|
|
3
|
+
import { basename, dirname, join } from "node:path";
|
|
4
|
+
import { errnoCode } from "@open-cr-agent/core/internal";
|
|
5
|
+
// Files only this user may read (the ocra Cloud session, the salts), written
|
|
6
|
+
// so that no reader, another ocra process included, ever sees one half
|
|
7
|
+
// written: the text goes to a temporary file in the same directory, created
|
|
8
|
+
// 0600, which then replaces the file in one rename.
|
|
9
|
+
/** Writes the file atomically, readable only by this user. */
|
|
10
|
+
export async function writePrivateFile(path, text) {
|
|
11
|
+
const temp = await writeTemp(path, text);
|
|
12
|
+
try {
|
|
13
|
+
await rename(temp, path);
|
|
14
|
+
}
|
|
15
|
+
catch (error) {
|
|
16
|
+
await rm(temp, { force: true });
|
|
17
|
+
throw error;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Writes the file atomically unless it exists: false when another writer
|
|
22
|
+
* made it first, whose content then stands.
|
|
23
|
+
*/
|
|
24
|
+
export async function createPrivateFile(path, text) {
|
|
25
|
+
const temp = await writeTemp(path, text);
|
|
26
|
+
try {
|
|
27
|
+
await link(temp, path);
|
|
28
|
+
return true;
|
|
29
|
+
}
|
|
30
|
+
catch (error) {
|
|
31
|
+
if (errnoCode(error) === "EEXIST")
|
|
32
|
+
return false;
|
|
33
|
+
throw error;
|
|
34
|
+
}
|
|
35
|
+
finally {
|
|
36
|
+
await rm(temp, { force: true });
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
async function writeTemp(path, text) {
|
|
40
|
+
await mkdir(dirname(path), { recursive: true, mode: 0o700 });
|
|
41
|
+
const temp = join(dirname(path), `.${basename(path)}.${randomBytes(6).toString("hex")}.tmp`);
|
|
42
|
+
// "wx": a fresh file, so the mode applies; never one that already exists.
|
|
43
|
+
await writeFile(temp, text, { mode: 0o600, flag: "wx" });
|
|
44
|
+
return temp;
|
|
45
|
+
}
|
|
46
|
+
//# sourceMappingURL=private-file.js.map
|
|
@@ -1,12 +1,18 @@
|
|
|
1
1
|
import type { CustomProvider } from "@open-cr-agent/core";
|
|
2
|
-
import {
|
|
3
|
-
export declare const CLOUD_PREFIX = "ocra-";
|
|
2
|
+
import type { CloudDeps } from "./deps.js";
|
|
4
3
|
/** Where the access token travels to the runtime; never a user's variable (OCRA_ is refused in config). */
|
|
5
4
|
export declare const CLOUD_TOKEN_ENV = "OCRA_CLOUD_ACCESS_TOKEN";
|
|
6
|
-
|
|
7
|
-
export
|
|
5
|
+
/** The run the models serve: how long it may last, and whether its runtime reads a key at each call. */
|
|
6
|
+
export type CloudRun = {
|
|
7
|
+
timeoutMs: number;
|
|
8
|
+
keyReadPerCall: boolean;
|
|
9
|
+
};
|
|
10
|
+
export type CloudProviders = {
|
|
8
11
|
providers: Record<string, CustomProvider>;
|
|
9
12
|
env: Env;
|
|
10
|
-
|
|
13
|
+
stop(): void;
|
|
14
|
+
};
|
|
15
|
+
type Env = Readonly<Record<string, string | undefined>>;
|
|
16
|
+
export declare function withCloudProviders(chains: readonly (readonly string[] | undefined)[], providers: Readonly<Record<string, CustomProvider>>, env: Env, deps: CloudDeps | undefined, warn: (message: string) => void, run?: CloudRun): Promise<CloudProviders>;
|
|
11
17
|
export {};
|
|
12
|
-
//# sourceMappingURL=
|
|
18
|
+
//# sourceMappingURL=providers.d.ts.map
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import { CLOUD_PREFIX, EFFORT_STYLES, GATEWAY_PATH, isCloudModel, } from "@open-cr-agent/cloud-contract";
|
|
2
|
+
import { errorMessage, OcraError } from "@open-cr-agent/core";
|
|
3
|
+
import { REVIEW_DEFAULTS } from "@open-cr-agent/core/internal";
|
|
4
|
+
import { CloudClient, sessionLostReason } from "./client.js";
|
|
5
|
+
import { GatewayToken } from "./gateway-token.js";
|
|
6
|
+
// Models named ocra-<provider>/<model> go through the ocra Cloud gateway with
|
|
7
|
+
// the key stored there for <provider> (ADR-0024). Each such provider becomes
|
|
8
|
+
// an OpenAI-compatible endpoint declared for this run, authorized by the
|
|
9
|
+
// signed-in session's access token. Without such a model nothing here reads
|
|
10
|
+
// the session or touches the network.
|
|
11
|
+
/** Where the access token travels to the runtime; never a user's variable (OCRA_ is refused in config). */
|
|
12
|
+
export const CLOUD_TOKEN_ENV = "OCRA_CLOUD_ACCESS_TOKEN";
|
|
13
|
+
// A review runs 25 minutes at most by default; start it with a token that
|
|
14
|
+
// outlives that, and ask for one that outlives a longer run.
|
|
15
|
+
const MIN_TOKEN_MS = 35 * 60_000;
|
|
16
|
+
const TOKEN_MARGIN_MS = 10 * 60_000;
|
|
17
|
+
// chains: every chain of the run, the tiers' and the agents' own.
|
|
18
|
+
export async function withCloudProviders(chains, providers, env, deps, warn, run = { timeoutMs: REVIEW_DEFAULTS.runTimeoutMs, keyReadPerCall: false }) {
|
|
19
|
+
const wanted = new Map();
|
|
20
|
+
const dropped = [];
|
|
21
|
+
for (const model of chains.flatMap((chain) => chain ?? [])) {
|
|
22
|
+
const slash = model.indexOf("/");
|
|
23
|
+
const id = model.slice(0, slash);
|
|
24
|
+
if (slash < 0 || !id.startsWith(CLOUD_PREFIX) || providers[id])
|
|
25
|
+
continue;
|
|
26
|
+
if (!isCloudModel(model)) {
|
|
27
|
+
dropped.push(JSON.stringify(model));
|
|
28
|
+
continue;
|
|
29
|
+
}
|
|
30
|
+
const set = wanted.get(id) ?? new Set();
|
|
31
|
+
set.add(model.slice(slash + 1));
|
|
32
|
+
wanted.set(id, set);
|
|
33
|
+
}
|
|
34
|
+
if (dropped.length > 0) {
|
|
35
|
+
warn(`ignoring ${dropped.join(", ")}: an ocra Cloud model is ${CLOUD_PREFIX}<provider>/<model>, with a provider of lowercase letters, digits and - and a model of letters, digits and ./:@_-`);
|
|
36
|
+
}
|
|
37
|
+
if (wanted.size === 0)
|
|
38
|
+
return { providers: { ...providers }, env, stop: () => { } };
|
|
39
|
+
const names = [...wanted.keys()].join(", ");
|
|
40
|
+
if (env.OCRA_CLOUD === "off") {
|
|
41
|
+
throw new OcraError("CONFIG_INVALID", `Models name ocra Cloud (${names}) but OCRA_CLOUD=off`);
|
|
42
|
+
}
|
|
43
|
+
const client = deps ? new CloudClient(deps) : undefined;
|
|
44
|
+
// A runtime that reads the key once needs a token that outlives the run;
|
|
45
|
+
// one that reads it at each call gets it renewed during the run.
|
|
46
|
+
const needed = run.timeoutMs + TOKEN_MARGIN_MS;
|
|
47
|
+
const minValidity = run.keyReadPerCall ? MIN_TOKEN_MS : Math.max(MIN_TOKEN_MS, needed);
|
|
48
|
+
const state = client ? await client.session(minValidity) : { kind: "signed-out" };
|
|
49
|
+
if (!client || !deps || state.kind === "signed-out") {
|
|
50
|
+
throw new OcraError("CONFIG_CREDENTIALS_MISSING", `Models name ocra Cloud (${names}): sign in with ocra login`);
|
|
51
|
+
}
|
|
52
|
+
if (state.kind === "revoked") {
|
|
53
|
+
throw new OcraError("CONFIG_CREDENTIALS_MISSING", `Models name ocra Cloud (${names}): ${sessionLostReason(state)}`);
|
|
54
|
+
}
|
|
55
|
+
const unreachable = (reason) => new OcraError("RUNTIME_START_FAILED", `Models name ocra Cloud (${names}) but ocra Cloud could not be reached (${reason})`);
|
|
56
|
+
if (state.kind === "unreachable")
|
|
57
|
+
throw unreachable(state.reason);
|
|
58
|
+
const session = state.credentials;
|
|
59
|
+
let answer;
|
|
60
|
+
try {
|
|
61
|
+
answer = await client.providers(session.server);
|
|
62
|
+
}
|
|
63
|
+
catch (error) {
|
|
64
|
+
throw unreachable(errorMessage(error));
|
|
65
|
+
}
|
|
66
|
+
if (answer.kind === "status" && (answer.status >= 500 || answer.status === 429)) {
|
|
67
|
+
throw unreachable(`HTTP ${answer.status}`);
|
|
68
|
+
}
|
|
69
|
+
if (answer.kind === "malformed") {
|
|
70
|
+
throw new OcraError("RUNTIME_START_FAILED", "ocra Cloud's answer for its providers is not a list of providers");
|
|
71
|
+
}
|
|
72
|
+
if (answer.kind !== "ok") {
|
|
73
|
+
throw new OcraError("RUNTIME_START_FAILED", `ocra Cloud answered ${answer.status} for its providers`);
|
|
74
|
+
}
|
|
75
|
+
const listed = answer.value;
|
|
76
|
+
const declared = { ...providers };
|
|
77
|
+
for (const [id, modelIds] of wanted) {
|
|
78
|
+
const name = id.slice(CLOUD_PREFIX.length);
|
|
79
|
+
const entry = listed.find((p) => p.name === name);
|
|
80
|
+
const paths = entry?.paths ?? [];
|
|
81
|
+
const valid = paths.filter((path) => typeof path === "string" && GATEWAY_PATH.test(path));
|
|
82
|
+
if (valid.length < paths.length) {
|
|
83
|
+
warn(`ignoring ${paths.length - valid.length} gateway path(s) ocra Cloud listed for "${name}" that are not plain paths`);
|
|
84
|
+
}
|
|
85
|
+
const chat = valid.find((path) => path.endsWith("/chat/completions"));
|
|
86
|
+
if (!chat) {
|
|
87
|
+
throw new OcraError("CONFIG_INVALID", `ocra Cloud has no OpenAI-compatible chat endpoint for "${name}" (named by ${id})`);
|
|
88
|
+
}
|
|
89
|
+
declared[id] = {
|
|
90
|
+
baseUrl: `${session.server}/api/gateway/${name}${chat.slice(0, -"/chat/completions".length)}`,
|
|
91
|
+
apiKeyEnv: CLOUD_TOKEN_ENV,
|
|
92
|
+
models: Object.fromEntries([...modelIds].map((m) => [m, { input: 0, output: 0 }])),
|
|
93
|
+
...effortStyleOf(entry?.effort, name, warn),
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
warn(`${names}: models through ocra Cloud are not priced, so reported cost and --max-cost-usd do not count them`);
|
|
97
|
+
if (!run.keyReadPerCall) {
|
|
98
|
+
const left = session.expires_at - deps.now();
|
|
99
|
+
if (left < needed) {
|
|
100
|
+
warn(`this run may last ${minutes(run.timeoutMs)} minutes, but its runtime reads the ocra Cloud token once and the token lasts ${minutes(left)}: model calls through ocra Cloud fail after that (use "runtime": "direct", which renews it, or a shorter runTimeoutMinutes)`);
|
|
101
|
+
}
|
|
102
|
+
return {
|
|
103
|
+
providers: declared,
|
|
104
|
+
env: { ...env, [CLOUD_TOKEN_ENV]: session.access_token },
|
|
105
|
+
stop: () => { },
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
const token = new GatewayToken(session, deps, warn);
|
|
109
|
+
token.start();
|
|
110
|
+
// The token is read at each call, so a renewed one is used from then on.
|
|
111
|
+
const live = Object.defineProperty({ ...env }, CLOUD_TOKEN_ENV, {
|
|
112
|
+
get: () => token.value,
|
|
113
|
+
enumerable: true,
|
|
114
|
+
});
|
|
115
|
+
return { providers: declared, env: live, stop: () => token.stop() };
|
|
116
|
+
}
|
|
117
|
+
function minutes(ms) {
|
|
118
|
+
return Math.floor(ms / 60_000);
|
|
119
|
+
}
|
|
120
|
+
// How the gateway's provider takes a reasoning effort, as ocra Cloud lists
|
|
121
|
+
// it; unlisted, the runtime's default (OpenAI's reasoning_effort). A style
|
|
122
|
+
// this version of ocra does not know is refused rather than guessed.
|
|
123
|
+
function effortStyleOf(style, name, warn) {
|
|
124
|
+
if (style === undefined)
|
|
125
|
+
return {};
|
|
126
|
+
const known = EFFORT_STYLES.find((s) => s === style);
|
|
127
|
+
if (known)
|
|
128
|
+
return { effort: known };
|
|
129
|
+
warn(`ignoring the effort style ocra Cloud listed for "${name}", which this version of ocra does not know; efforts go as OpenAI's reasoning_effort`);
|
|
130
|
+
return {};
|
|
131
|
+
}
|
|
132
|
+
//# sourceMappingURL=providers.js.map
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { ReviewSource } from "@open-cr-agent/cloud-contract";
|
|
2
|
+
import type { MemoryEntry, ReviewReport } from "@open-cr-agent/core";
|
|
3
|
+
import type { Output } from "../io/output.js";
|
|
4
|
+
import { type CloudSessionLost } from "./client.js";
|
|
5
|
+
import type { CloudDeps } from "./deps.js";
|
|
6
|
+
export type CloudReview = {
|
|
7
|
+
repoHash: string;
|
|
8
|
+
shareFindings: boolean;
|
|
9
|
+
memory: MemoryEntry[];
|
|
10
|
+
};
|
|
11
|
+
/** What a review loses when ocra Cloud cannot be used for it, said once. */
|
|
12
|
+
export declare function sessionLostWarning(lost: Exclude<CloudSessionLost, {
|
|
13
|
+
kind: "signed-out";
|
|
14
|
+
}>): string;
|
|
15
|
+
/**
|
|
16
|
+
* Undefined when the saved session is gone; a failure to reach ocra Cloud is
|
|
17
|
+
* one warning. The repository is the pull or merge request's when given,
|
|
18
|
+
* else origin's.
|
|
19
|
+
*/
|
|
20
|
+
export declare function prepareCloudReview(root: string, deps: CloudDeps, warn: (message: string) => void, repository?: string): Promise<CloudReview | undefined>;
|
|
21
|
+
/** Sends the counts, and the findings when the account shares them. */
|
|
22
|
+
export declare function sendToCloud(report: ReviewReport, cloud: CloudReview, deps: CloudDeps, run: {
|
|
23
|
+
source: ReviewSource;
|
|
24
|
+
durationMs: number;
|
|
25
|
+
err: Output;
|
|
26
|
+
warn: (message: string) => void;
|
|
27
|
+
}): Promise<void>;
|
|
28
|
+
//# sourceMappingURL=review.d.ts.map
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { originRepository } from "../repository-id.js";
|
|
2
|
+
import { readAccountSalt, saveAccountSalt } from "./account-salt.js";
|
|
3
|
+
import { CloudClient, sessionLostReason } from "./client.js";
|
|
4
|
+
import { sharedFindings } from "./findings.js";
|
|
5
|
+
import { accountHasMemory, fetchAccountMemory } from "./memory.js";
|
|
6
|
+
import { repoHash, uploadOf, uploadReview } from "./upload.js";
|
|
7
|
+
/** What a review loses when ocra Cloud cannot be used for it, said once. */
|
|
8
|
+
export function sessionLostWarning(lost) {
|
|
9
|
+
const cause = lost.kind === "revoked"
|
|
10
|
+
? sessionLostReason(lost)
|
|
11
|
+
: `could not reach ocra Cloud (${lost.reason})`;
|
|
12
|
+
return `${cause}; this review runs without your account's rules, limits (maxCostUsd included), models and memory, and uploads nothing`;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Undefined when the saved session is gone; a failure to reach ocra Cloud is
|
|
16
|
+
* one warning. The repository is the pull or merge request's when given,
|
|
17
|
+
* else origin's.
|
|
18
|
+
*/
|
|
19
|
+
export async function prepareCloudReview(root, deps, warn, repository) {
|
|
20
|
+
const id = repository ?? (await originRepository(root));
|
|
21
|
+
let salt;
|
|
22
|
+
try {
|
|
23
|
+
const answer = await new CloudClient(deps).accountSalt();
|
|
24
|
+
if (answer.kind === "signed-out")
|
|
25
|
+
return undefined;
|
|
26
|
+
if (answer.kind === "status")
|
|
27
|
+
throw new Error(`HTTP ${answer.status}`);
|
|
28
|
+
if (answer.kind === "malformed")
|
|
29
|
+
throw new Error("the answer is not a salt");
|
|
30
|
+
if (answer.kind !== "ok")
|
|
31
|
+
throw new Error(sessionLostReason(answer));
|
|
32
|
+
salt = answer.value;
|
|
33
|
+
}
|
|
34
|
+
catch (error) {
|
|
35
|
+
// The salt kept from the last answer, else this machine's, still groups
|
|
36
|
+
// the counts. Whether the account still shares findings is unknown, so
|
|
37
|
+
// none is sent and the account's memory is not applied.
|
|
38
|
+
warn(`could not read your ocra Cloud account (${error instanceof Error ? error.message : "error"}); this review sends no findings and applies no account memory`);
|
|
39
|
+
const kept = await readAccountSalt(deps.credentialsPath);
|
|
40
|
+
return {
|
|
41
|
+
repoHash: await repoHash(id, deps.credentialsPath, kept),
|
|
42
|
+
shareFindings: false,
|
|
43
|
+
memory: [],
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
await saveAccountSalt(deps.credentialsPath, salt);
|
|
47
|
+
const hash = await repoHash(id, deps.credentialsPath, salt ?? undefined);
|
|
48
|
+
if (salt === null) {
|
|
49
|
+
// Memory is keyed by the account's hash, which only a sharing account has.
|
|
50
|
+
if (await accountHasMemory(deps)) {
|
|
51
|
+
warn("your ocra Cloud account remembers findings, but applies them only while it shares findings (Settings in ocra Cloud); this review applies the repository's memory alone");
|
|
52
|
+
}
|
|
53
|
+
return { repoHash: hash, shareFindings: false, memory: [] };
|
|
54
|
+
}
|
|
55
|
+
return {
|
|
56
|
+
repoHash: hash,
|
|
57
|
+
shareFindings: true,
|
|
58
|
+
memory: await fetchAccountMemory(deps, hash, warn),
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
/** Sends the counts, and the findings when the account shares them. */
|
|
62
|
+
export async function sendToCloud(report, cloud, deps, run) {
|
|
63
|
+
const upload = uploadOf(report, run.source, cloud.repoHash, run.durationMs);
|
|
64
|
+
const shared = cloud.shareFindings ? sharedFindings(report) : undefined;
|
|
65
|
+
const sent = await uploadReview(upload, deps, run.warn, shared?.findings);
|
|
66
|
+
if (!sent)
|
|
67
|
+
return;
|
|
68
|
+
if (sent.findings > 0) {
|
|
69
|
+
const left = shared?.left ? ` (${shared.left} left out by the size limit)` : "";
|
|
70
|
+
run.err.write(`[ocra] Sent this review's counts and ${sent.findings} finding(s) to ocra Cloud${left} (--no-upload to skip)\n`);
|
|
71
|
+
}
|
|
72
|
+
else {
|
|
73
|
+
run.err.write("[ocra] Sent this review's counts to ocra Cloud (--no-upload to skip)\n");
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
//# sourceMappingURL=review.js.map
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { SettingsLayer } from "../config/settings.js";
|
|
2
|
+
import { type AccountPlugins } from "../plugins/account.js";
|
|
3
|
+
import { type AccountSettings } from "./account-settings.js";
|
|
4
|
+
import { type CloudSessionLost } from "./client.js";
|
|
5
|
+
import type { CloudDeps } from "./deps.js";
|
|
6
|
+
export type AccountRead = {
|
|
7
|
+
kind: "read";
|
|
8
|
+
settings?: AccountSettings;
|
|
9
|
+
plugins: AccountPlugins;
|
|
10
|
+
} | CloudSessionLost;
|
|
11
|
+
/**
|
|
12
|
+
* The account's settings. A server that cannot be reached, or a session
|
|
13
|
+
* that ended, is answered for the caller to say once what the run loses;
|
|
14
|
+
* an answer that cannot be used is a warning here and no settings.
|
|
15
|
+
*/
|
|
16
|
+
export declare function fetchAccountSettings(deps: CloudDeps, warn: (message: string) => void): Promise<AccountRead>;
|
|
17
|
+
/**
|
|
18
|
+
* The account's settings as the layer under the configuration: they fill
|
|
19
|
+
* what it leaves out, and add to its include, exclude and rules.
|
|
20
|
+
*/
|
|
21
|
+
export declare function accountLayer(account: AccountSettings): SettingsLayer;
|
|
22
|
+
//# sourceMappingURL=settings.d.ts.map
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { errorMessage } from "@open-cr-agent/core";
|
|
2
|
+
import { parseAccountPlugins } from "../plugins/account.js";
|
|
3
|
+
import { parseAccountSettings } from "./account-settings.js";
|
|
4
|
+
import { CloudClient } from "./client.js";
|
|
5
|
+
const NO_PLUGINS = { plugins: [], pluginSettings: {} };
|
|
6
|
+
/**
|
|
7
|
+
* The account's settings. A server that cannot be reached, or a session
|
|
8
|
+
* that ended, is answered for the caller to say once what the run loses;
|
|
9
|
+
* an answer that cannot be used is a warning here and no settings.
|
|
10
|
+
*/
|
|
11
|
+
export async function fetchAccountSettings(deps, warn) {
|
|
12
|
+
let answer;
|
|
13
|
+
try {
|
|
14
|
+
answer = await new CloudClient(deps).preferences();
|
|
15
|
+
}
|
|
16
|
+
catch (error) {
|
|
17
|
+
return { kind: "unreachable", reason: errorMessage(error) };
|
|
18
|
+
}
|
|
19
|
+
if (answer.kind === "status") {
|
|
20
|
+
// A server without account settings has none to apply.
|
|
21
|
+
if (answer.status === 404)
|
|
22
|
+
return { kind: "read", plugins: NO_PLUGINS };
|
|
23
|
+
if (answer.status >= 500 || answer.status === 429) {
|
|
24
|
+
return { kind: "unreachable", reason: `HTTP ${answer.status}` };
|
|
25
|
+
}
|
|
26
|
+
warn(`could not read your ocra Cloud settings (HTTP ${answer.status}); the review uses the repository's`);
|
|
27
|
+
return { kind: "read", plugins: NO_PLUGINS };
|
|
28
|
+
}
|
|
29
|
+
if (answer.kind === "malformed") {
|
|
30
|
+
warn("ignoring your ocra Cloud settings: the server's answer is not an object");
|
|
31
|
+
return { kind: "read", plugins: NO_PLUGINS };
|
|
32
|
+
}
|
|
33
|
+
if (answer.kind !== "ok")
|
|
34
|
+
return answer;
|
|
35
|
+
const body = answer.value;
|
|
36
|
+
const { settings, warnings } = parseAccountSettings(body);
|
|
37
|
+
for (const warning of warnings)
|
|
38
|
+
warn(warning);
|
|
39
|
+
return {
|
|
40
|
+
kind: "read",
|
|
41
|
+
...(settings ? { settings } : {}),
|
|
42
|
+
plugins: parseAccountPlugins(body, warn),
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* The account's settings as the layer under the configuration: they fill
|
|
47
|
+
* what it leaves out, and add to its include, exclude and rules.
|
|
48
|
+
*/
|
|
49
|
+
export function accountLayer(account) {
|
|
50
|
+
const { version: _, rules, ultra, ...settings } = account;
|
|
51
|
+
return {
|
|
52
|
+
source: "account",
|
|
53
|
+
under: true,
|
|
54
|
+
settings: {
|
|
55
|
+
...settings,
|
|
56
|
+
...(rules
|
|
57
|
+
? { rules: rules.map((rule) => ({ ...rule, source: "account" })) }
|
|
58
|
+
: {}),
|
|
59
|
+
// The account's default for --ultra; there is no --no-ultra to refuse it.
|
|
60
|
+
...(ultra === true ? { ultra } : {}),
|
|
61
|
+
},
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
//# sourceMappingURL=settings.js.map
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { ReviewSource, ReviewUpload, SharedFinding } from "@open-cr-agent/cloud-contract";
|
|
2
|
+
import { type ReviewReport } from "@open-cr-agent/core";
|
|
3
|
+
import type { CloudDeps } from "./deps.js";
|
|
4
|
+
export declare function uploadOf(report: ReviewReport, source: ReviewSource, repoHash: string, durationMs: number): ReviewUpload;
|
|
5
|
+
/**
|
|
6
|
+
* The hash of a repository named as `https://host/owner/repo`
|
|
7
|
+
* (repository-id.ts), salted with the account's salt when given, else this machine's.
|
|
8
|
+
*/
|
|
9
|
+
export declare function repoHash(repository: string, credentialsPath: string, accountSalt?: string): Promise<string>;
|
|
10
|
+
/** Whether this run talks to ocra Cloud at all: signed in, not turned off. */
|
|
11
|
+
export declare function cloudEnabled(deps: CloudDeps, warn: (message: string) => void): Promise<boolean>;
|
|
12
|
+
/**
|
|
13
|
+
* Sends the counts, and the findings when given; answers how many findings
|
|
14
|
+
* the server kept, or undefined when it took nothing. A failure is a
|
|
15
|
+
* warning, never a failed review.
|
|
16
|
+
*/
|
|
17
|
+
export declare function uploadReview(upload: ReviewUpload, deps: CloudDeps, warn: (message: string) => void, findings?: readonly SharedFinding[]): Promise<{
|
|
18
|
+
findings: number;
|
|
19
|
+
} | undefined>;
|
|
20
|
+
//# sourceMappingURL=upload.d.ts.map
|