windows-development-sandbox 1.11.7 → 1.13.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/README.md +13 -6
- package/config.example.json +15 -4
- package/dist/app/features/access/access.def.d.ts +190 -0
- package/dist/app/features/access/access.def.js +273 -0
- package/dist/app/features/access/access.run.d.ts +79 -0
- package/dist/app/features/access/access.run.js +129 -0
- package/dist/app/features/access/index.d.ts +2 -0
- package/dist/app/features/access/index.js +3 -0
- package/dist/app/features/agents/agent-entry.js +5 -0
- package/dist/app/features/agents/agents.def.js +1 -0
- package/dist/app/features/apps/apps-rows.d.ts +4 -1
- package/dist/app/features/apps/apps-rows.js +7 -2
- package/dist/app/features/apps/apps.def.d.ts +19 -0
- package/dist/app/features/apps/apps.def.js +50 -12
- package/dist/app/features/apps/apps.run.d.ts +2 -0
- package/dist/app/features/apps/apps.run.js +1 -1
- package/dist/app/features/audit/audit.def.d.ts +350 -0
- package/dist/app/features/audit/audit.def.js +457 -0
- package/dist/app/features/audit/audit.run.d.ts +110 -0
- package/dist/app/features/audit/audit.run.js +133 -0
- package/dist/app/features/audit/index.d.ts +2 -0
- package/dist/app/features/audit/index.js +3 -0
- package/dist/app/features/defs.d.ts +349 -4
- package/dist/app/features/defs.js +16 -1
- package/dist/app/features/files/files.def.js +1 -0
- package/dist/app/features/health/health.def.js +1 -0
- package/dist/app/features/sandbox/sandbox.def.js +3 -0
- package/dist/app/features/sessions/sessions.def.d.ts +36 -0
- package/dist/app/features/sessions/sessions.def.js +77 -9
- package/dist/app/features/sessions/sessions.run.d.ts +25 -2
- package/dist/app/features/sessions/sessions.run.js +71 -3
- package/dist/app/features/settings/settings.def.d.ts +8 -0
- package/dist/app/features/settings/settings.def.js +17 -0
- package/dist/app/features/settings/settings.run.d.ts +3 -0
- package/dist/app/features/settings/settings.run.js +16 -2
- package/dist/app/features/web/web.def.d.ts +9 -0
- package/dist/app/features/web/web.def.js +26 -1
- package/dist/app/features/web/web.run.d.ts +3 -0
- package/dist/app/features/web/web.run.js +11 -3
- package/dist/app/kernel/access.d.ts +33 -0
- package/dist/app/kernel/access.js +44 -0
- package/dist/app/kernel/define.d.ts +26 -2
- package/dist/app/kernel/index.d.ts +1 -0
- package/dist/app/kernel/index.js +1 -0
- package/dist/app/kernel/rpc.d.ts +5 -2
- package/dist/app/registry.d.ts +4 -1
- package/dist/app/registry.js +26 -4
- package/dist/cli/args.d.ts +0 -3
- package/dist/cli/args.js +3 -4
- package/dist/cli/commands/index.d.ts +1 -1
- package/dist/cli/commands/index.js +1 -1
- package/dist/cli/commands/launch/prepare.js +1 -1
- package/dist/cli/commands/maintenance/index.d.ts +0 -1
- package/dist/cli/commands/maintenance/index.js +0 -1
- package/dist/cli/commands/registry.js +3 -2
- package/dist/cli/commands/remote.d.ts +2 -0
- package/dist/cli/commands/remote.js +49 -0
- package/dist/cli/commands/sessions/reopen.js +3 -0
- package/dist/cli/commands/web.js +66 -0
- package/dist/cli/manifest.d.ts +63 -45
- package/dist/cli/manifest.js +33 -18
- package/dist/cli/menu/io-menu.d.ts +5 -3
- package/dist/cli/menu/io-menu.js +12 -5
- package/dist/cli/menu/pages/apps-page.js +2 -15
- package/dist/cli/menu/pages/maintenance-page.d.ts +4 -0
- package/dist/cli/menu/pages/maintenance-page.js +51 -8
- package/dist/cli/menu/remote-menu.d.ts +19 -0
- package/dist/cli/menu/remote-menu.js +108 -0
- package/dist/cli/menu/views.js +4 -0
- package/dist/cli/operations/handler.js +15 -1
- package/dist/cli/services/remote-client.d.ts +16 -0
- package/dist/cli/services/remote-client.js +141 -0
- package/dist/cli/services/sessions-service.d.ts +5 -0
- package/dist/cli/services/sessions-service.js +17 -1
- package/dist/core/config/defaults.js +12 -2
- package/dist/core/local-config/file.js +16 -2
- package/dist/core/local-config/overrides.js +7 -0
- package/dist/core/run/versions.js +9 -8
- package/dist/core/schema/apps.d.ts +9 -0
- package/dist/core/schema/apps.js +9 -0
- package/dist/core/schema/config.d.ts +23 -3
- package/dist/core/schema/config.js +42 -5
- package/dist/core/schema/sandbox.d.ts +1 -0
- package/dist/core/schema/sandbox.js +11 -3
- package/dist/core/schema/web.d.ts +51 -0
- package/dist/core/schema/web.js +46 -1
- package/dist/core/util/fs.d.ts +5 -0
- package/dist/core/util/fs.js +23 -0
- package/dist/core/util/index.d.ts +1 -1
- package/dist/core/util/index.js +1 -1
- package/dist/guest/apps/app-installer.js +50 -4
- package/dist/guest/apps/index.d.ts +1 -1
- package/dist/guest/apps/index.js +1 -1
- package/dist/guest/apps/sources.js +1 -1
- package/dist/guest/audit/parse.d.ts +32 -2
- package/dist/guest/audit/parse.js +43 -5
- package/dist/guest/audit/process-audit.d.ts +3 -1
- package/dist/guest/audit/process-audit.js +9 -6
- package/dist/guest/boot/boot.js +12 -3
- package/dist/guest/boot/guide.d.ts +1 -1
- package/dist/guest/boot/guide.js +27 -3
- package/dist/guest/daemon/daemon.js +2 -0
- package/dist/guest/daemon/idle-shutdown.js +2 -1
- package/dist/guest/daemon/sessions.d.ts +23 -2
- package/dist/guest/daemon/sessions.js +151 -20
- package/dist/guest/entries/daemon.js +2 -1
- package/dist/guest/git/decision.js +7 -0
- package/dist/guest/sessions/cmdline.d.ts +14 -0
- package/dist/guest/sessions/cmdline.js +10 -0
- package/dist/guest/setup/addon-mcp.d.ts +2 -0
- package/dist/guest/setup/addon-mcp.js +49 -0
- package/dist/guest/setup/agent-addons.d.ts +9 -0
- package/dist/guest/setup/agent-addons.js +22 -0
- package/dist/guest/setup/headroom.d.ts +7 -0
- package/dist/guest/setup/headroom.js +60 -0
- package/dist/guest/setup/managed-policy.d.ts +2 -1
- package/dist/guest/setup/managed-policy.js +15 -6
- package/dist/guest/toolchains/plan.js +8 -3
- package/dist/guest/toolchains/setup.js +3 -0
- package/dist/host/audit/attribute.d.ts +46 -0
- package/dist/host/audit/attribute.js +76 -0
- package/dist/host/audit/collect.d.ts +29 -3
- package/dist/host/audit/collect.js +256 -61
- package/dist/host/audit/git-commits.d.ts +29 -0
- package/dist/host/audit/git-commits.js +157 -0
- package/dist/host/audit/index.d.ts +5 -0
- package/dist/host/audit/index.js +6 -0
- package/dist/host/audit/log.d.ts +45 -7
- package/dist/host/audit/log.js +148 -45
- package/dist/host/audit/redact.d.ts +2 -0
- package/dist/host/audit/redact.js +67 -0
- package/dist/host/channel/channel.d.ts +5 -1
- package/dist/host/channel/channel.js +12 -0
- package/dist/host/guard/agent-files.js +7 -1
- package/dist/host/guard/commands.js +13 -1
- package/dist/host/guard/git-config.js +27 -7
- package/dist/host/guard/git-dirs.js +78 -3
- package/dist/host/guard/mark-of-the-web.js +3 -1
- package/dist/host/guard/nested-repos.js +7 -2
- package/dist/host/guard/store.js +1 -0
- package/dist/host/guard/types.d.ts +2 -0
- package/dist/host/launch/mappings.js +11 -1
- package/dist/host/launch/project-scan.js +2 -21
- package/dist/host/launch/stage.js +4 -0
- package/dist/host/life/session-ledger.js +2 -1
- package/dist/host/remote/access-store.d.ts +156 -0
- package/dist/host/remote/access-store.js +308 -0
- package/dist/host/remote/cert.d.ts +20 -0
- package/dist/host/remote/cert.js +103 -0
- package/dist/host/remote/door-client.d.ts +13 -0
- package/dist/host/remote/door-client.js +54 -0
- package/dist/host/remote/firewall.d.ts +44 -0
- package/dist/host/remote/firewall.js +118 -0
- package/dist/host/remote/gate.d.ts +125 -0
- package/dist/host/remote/gate.js +391 -0
- package/dist/host/remote/index.d.ts +8 -0
- package/dist/host/remote/index.js +10 -0
- package/dist/host/remote/lan.d.ts +24 -0
- package/dist/host/remote/lan.js +88 -0
- package/dist/host/remote/tls-store.d.ts +22 -0
- package/dist/host/remote/tls-store.js +63 -0
- package/dist/host/remote/windows-helper.d.ts +67 -0
- package/dist/host/remote/windows-helper.js +129 -0
- package/dist/host/web/bridge.d.ts +5 -1
- package/dist/host/web/bridge.js +6 -2
- package/dist/host/web/browser.js +1 -1
- package/dist/host/web/control.d.ts +4 -1
- package/dist/host/web/control.js +28 -4
- package/dist/host/web/rpc.d.ts +16 -2
- package/dist/host/web/rpc.js +37 -4
- package/dist/host/web/runner.d.ts +4 -0
- package/dist/host/web/runner.js +12 -0
- package/dist/host/web/server.d.ts +18 -2
- package/dist/host/web/server.js +97 -24
- package/dist/host/web/state.d.ts +5 -0
- package/dist/host/web/state.js +8 -0
- package/dist/runtime/agent.js +184 -18
- package/dist/runtime/daemon.js +772 -338
- package/dist/runtime/db.js +184 -18
- package/dist/runtime/gitguard.js +6 -0
- package/dist/runtime/main.js +966 -553
- package/dist/runtime/netproxy.js +184 -18
- package/dist/runtime/view.js +186 -18
- package/dist/selftest/app.test.js +66 -7
- package/dist/selftest/audit.test.js +354 -47
- package/dist/selftest/channel.test.js +16 -0
- package/dist/selftest/cmdline.test.js +16 -0
- package/dist/selftest/git-access.test.js +8 -0
- package/dist/selftest/hcs.test.js +35 -0
- package/dist/selftest/hostile-input.test.js +12 -1
- package/dist/selftest/index.js +2 -0
- package/dist/selftest/live-daemon.test.js +25 -0
- package/dist/selftest/local-config.test.js +30 -0
- package/dist/selftest/menu-screens.js +1 -1
- package/dist/selftest/menu.test.js +3 -3
- package/dist/selftest/remote.test.d.ts +2 -0
- package/dist/selftest/remote.test.js +536 -0
- package/dist/selftest/repo-guard.test.js +56 -2
- package/dist/selftest/resume.test.js +2 -1
- package/dist/selftest/sharing.test.js +9 -2
- package/dist/selftest/support/engine-golden.js +9 -0
- package/dist/selftest/support/fixtures.d.ts +14 -0
- package/dist/selftest/support/web-preview.js +157 -0
- package/dist/selftest/toolchains.test.js +147 -2
- package/dist/selftest/web.test.js +2 -0
- package/dist/shared/apps/agent-addons.d.ts +64 -0
- package/dist/shared/apps/agent-addons.js +95 -0
- package/dist/shared/apps/catalogue.d.ts +33 -2
- package/dist/shared/apps/catalogue.js +102 -11
- package/dist/shared/apps/index.d.ts +3 -2
- package/dist/shared/apps/index.js +3 -2
- package/dist/shared/apps/selection.d.ts +2 -0
- package/dist/shared/apps/selection.js +7 -0
- package/dist/shared/channel/schema.d.ts +23 -0
- package/dist/shared/channel/schema.js +8 -0
- package/dist/shared/pty/protocol.d.ts +2 -0
- package/dist/shared/pty/protocol.js +2 -0
- package/dist/sync/watcher.js +9 -1
- package/dist/web/index.html +2 -2
- package/dist/web/{main-RQFN4WBY.css → main-UAQPI5XD.css} +1 -1
- package/dist/web/main-Z2WYAE2Z.js +221 -0
- package/native/csharp/HcsHost/AssemblyInfo.cs +5 -3
- package/native/csharp/HcsHost/Hcs.cs +9 -0
- package/native/csharp/HcsHost/Program.cs +26 -1
- package/native/csharp/HcsHost/Share.cs +173 -0
- package/native/csharp/HostTools/AssemblyInfo.cs +3 -3
- package/native/csharp/HostTools/Program.cs +17 -1
- package/native/csharp/HostTools/WebHelper.cs +499 -0
- package/native/scripts/get-network-profile.ps1 +19 -0
- package/native/scripts/set-folder-access.ps1 +11 -0
- package/native/scripts/set-web-firewall.ps1 +103 -0
- package/package.json +1 -1
- package/sandbox/addons/ccusage/package-lock.json +111 -0
- package/sandbox/addons/ccusage/package.json +7 -0
- package/sandbox/addons/context7/package-lock.json +1313 -0
- package/sandbox/addons/context7/package.json +7 -0
- package/sandbox/addons/headroom/requirements.in +1 -0
- package/sandbox/addons/headroom/requirements.txt +2922 -0
- package/sandbox/addons/playwright-mcp/package-lock.json +56 -0
- package/sandbox/addons/playwright-mcp/package.json +7 -0
- package/sandbox/csharp/PtyHost.cs +20 -0
- package/schemas/config.schema.json +78 -4
- package/schemas/sandbox-config.schema.json +2 -20
- package/dist/cli/commands/maintenance/audit.d.ts +0 -3
- package/dist/cli/commands/maintenance/audit.js +0 -58
- package/dist/web/main-K2JMCAZI.js +0 -219
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/** A session as the audit remembers it (its run record goes with the sandbox). */
|
|
2
|
+
export interface KnownSession {
|
|
3
|
+
readonly id: string;
|
|
4
|
+
readonly agent: string;
|
|
5
|
+
/** The project it runs in (hub name). */
|
|
6
|
+
readonly project: string;
|
|
7
|
+
/** When it started and ended (ms). */
|
|
8
|
+
readonly from: number;
|
|
9
|
+
readonly to?: number;
|
|
10
|
+
}
|
|
11
|
+
/** A git command a session ran that can make a commit (from the programs the sandbox reported). */
|
|
12
|
+
export interface GitRun {
|
|
13
|
+
readonly t: number;
|
|
14
|
+
readonly session: string;
|
|
15
|
+
}
|
|
16
|
+
/** A link to a session: its id and how it was made (p: process tree / git command; t: only session running). */
|
|
17
|
+
export interface Link {
|
|
18
|
+
readonly s: string;
|
|
19
|
+
readonly sv: 'p' | 't';
|
|
20
|
+
}
|
|
21
|
+
/** git commit, merge, cherry-pick, revert, rebase, am, pull: the commands that can add a commit. */
|
|
22
|
+
export declare function makesCommits(program: string, commandLine: string): boolean;
|
|
23
|
+
/** The sessions of this sandbox seen so far, with when each ran. */
|
|
24
|
+
export declare class SessionBook {
|
|
25
|
+
private readonly sessions;
|
|
26
|
+
/** Takes the sessions as they are now: new ones are added, ended ones get their end time. */
|
|
27
|
+
update(list: readonly {
|
|
28
|
+
readonly id: string;
|
|
29
|
+
readonly harness: string;
|
|
30
|
+
readonly project?: string;
|
|
31
|
+
readonly phase: string;
|
|
32
|
+
readonly startedAt: string;
|
|
33
|
+
readonly updatedAt: string;
|
|
34
|
+
}[], fallbackProject: string, now?: number): void;
|
|
35
|
+
get(id: string | undefined): KnownSession | undefined;
|
|
36
|
+
/** The one session running in this project at time t, or undefined when none or several were. */
|
|
37
|
+
onlyRunning(project: string, t: number): KnownSession | undefined;
|
|
38
|
+
/** A file change in a project at time t: linked only when one session ran there then. */
|
|
39
|
+
linkFile(project: string, t: number): Link | undefined;
|
|
40
|
+
/**
|
|
41
|
+
* A commit in a project at time t: the session whose git command made it when that project's sessions ran git
|
|
42
|
+
* commands that make commits around then and all were the same session's; else, when `likely` is allowed (programs
|
|
43
|
+
* are not recorded, so no git command could be seen), the one session running there.
|
|
44
|
+
*/
|
|
45
|
+
linkCommit(project: string, t: number, gitRuns: readonly GitRun[], likely?: boolean): Link | undefined;
|
|
46
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
// Which agent session an event belongs to, on the PC side. Programs come linked from the sandbox (by the process tree,
|
|
2
|
+
// guest/audit/parse.ts); the host only keeps a link to a session it knows. Files and commits are seen from this PC,
|
|
3
|
+
// where the writer cannot be told: they are linked only when exactly one session was running in that project at the
|
|
4
|
+
// time ("likely", marked as such), or, for a commit, when that project's sessions ran exactly one git command that
|
|
5
|
+
// makes commits around then. Anything else stays "no session": nothing is guessed. Pure, so the self-test checks it.
|
|
6
|
+
import path from 'node:path';
|
|
7
|
+
/** How long before and after a commit a git command counts as the one that made it. */
|
|
8
|
+
const GIT_BEFORE_MS = 3 * 60 * 1000;
|
|
9
|
+
const GIT_AFTER_MS = 30 * 1000;
|
|
10
|
+
/** Sessions remembered at most. */
|
|
11
|
+
const MAX_SESSIONS = 512;
|
|
12
|
+
/** git commit, merge, cherry-pick, revert, rebase, am, pull: the commands that can add a commit. */
|
|
13
|
+
export function makesCommits(program, commandLine) {
|
|
14
|
+
return (/^git(\.exe)?$/i.test(path.win32.basename(program)) &&
|
|
15
|
+
/\s(commit|merge|cherry-pick|revert|rebase|am|pull)(\s|$)/i.test(commandLine));
|
|
16
|
+
}
|
|
17
|
+
/** The sessions of this sandbox seen so far, with when each ran. */
|
|
18
|
+
export class SessionBook {
|
|
19
|
+
sessions = new Map();
|
|
20
|
+
/** Takes the sessions as they are now: new ones are added, ended ones get their end time. */
|
|
21
|
+
update(list, fallbackProject, now = Date.now()) {
|
|
22
|
+
for (const s of list) {
|
|
23
|
+
const known = this.sessions.get(s.id);
|
|
24
|
+
const ended = s.phase === 'exited' || s.phase === 'failed';
|
|
25
|
+
if (known) {
|
|
26
|
+
if (ended && known.to === undefined)
|
|
27
|
+
this.sessions.set(s.id, { ...known, to: Math.min(now, Date.parse(s.updatedAt) || now) });
|
|
28
|
+
continue;
|
|
29
|
+
}
|
|
30
|
+
if (this.sessions.size >= MAX_SESSIONS)
|
|
31
|
+
continue;
|
|
32
|
+
const from = Date.parse(s.startedAt) || now;
|
|
33
|
+
this.sessions.set(s.id, {
|
|
34
|
+
id: s.id,
|
|
35
|
+
agent: s.harness,
|
|
36
|
+
project: s.project ?? fallbackProject,
|
|
37
|
+
from,
|
|
38
|
+
...(ended ? { to: Math.max(from, Date.parse(s.updatedAt) || now) } : {}),
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
// A session whose record is gone is not running any more: it ended by now at the latest.
|
|
42
|
+
const listed = new Set(list.map((s) => s.id));
|
|
43
|
+
for (const [id, s] of this.sessions)
|
|
44
|
+
if (!listed.has(id) && s.to === undefined)
|
|
45
|
+
this.sessions.set(id, { ...s, to: now });
|
|
46
|
+
}
|
|
47
|
+
get(id) {
|
|
48
|
+
return id === undefined ? undefined : this.sessions.get(id);
|
|
49
|
+
}
|
|
50
|
+
/** The one session running in this project at time t, or undefined when none or several were. */
|
|
51
|
+
onlyRunning(project, t) {
|
|
52
|
+
const running = [...this.sessions.values()].filter((s) => s.project.toLowerCase() === project.toLowerCase() && s.from <= t && (s.to === undefined || t <= s.to));
|
|
53
|
+
return running.length === 1 ? running[0] : undefined;
|
|
54
|
+
}
|
|
55
|
+
/** A file change in a project at time t: linked only when one session ran there then. */
|
|
56
|
+
linkFile(project, t) {
|
|
57
|
+
const only = this.onlyRunning(project, t);
|
|
58
|
+
return only ? { s: only.id, sv: 't' } : undefined;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* A commit in a project at time t: the session whose git command made it when that project's sessions ran git
|
|
62
|
+
* commands that make commits around then and all were the same session's; else, when `likely` is allowed (programs
|
|
63
|
+
* are not recorded, so no git command could be seen), the one session running there.
|
|
64
|
+
*/
|
|
65
|
+
linkCommit(project, t, gitRuns, likely = true) {
|
|
66
|
+
const near = new Set(gitRuns
|
|
67
|
+
.filter((g) => g.t >= t - GIT_BEFORE_MS && g.t <= t + GIT_AFTER_MS)
|
|
68
|
+
.map((g) => g.session)
|
|
69
|
+
.filter((id) => this.get(id)?.project.toLowerCase() === project.toLowerCase()));
|
|
70
|
+
if (near.size === 1)
|
|
71
|
+
return { s: [...near][0], sv: 'p' };
|
|
72
|
+
if (near.size > 1)
|
|
73
|
+
return undefined;
|
|
74
|
+
return likely ? this.linkFile(project, t) : undefined;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
@@ -5,29 +5,55 @@ declare const AuditSettingsSchema: z.ZodObject<{
|
|
|
5
5
|
maxMb: z.ZodNumber;
|
|
6
6
|
processes: z.ZodBoolean;
|
|
7
7
|
files: z.ZodBoolean;
|
|
8
|
+
commits: z.ZodDefault<z.ZodBoolean>;
|
|
8
9
|
projects: z.ZodArray<z.ZodString>;
|
|
9
10
|
}, z.core.$strip>;
|
|
10
11
|
export type AuditSettings = z.output<typeof AuditSettingsSchema>;
|
|
11
12
|
/** Host-only: the audit settings of the running sandbox (written at launch, read by the watcher). */
|
|
12
13
|
export declare function saveAuditSettings(storageRoot: string, dataPath: string, settings: AuditSettings): void;
|
|
13
14
|
export declare function readAuditSettings(storageRoot: string, dataPath: string): AuditSettings | null;
|
|
15
|
+
/** A project of the running sandbox: its name (as sessions name it) and its folder on this PC. */
|
|
16
|
+
export interface AuditProject {
|
|
17
|
+
readonly name: string;
|
|
18
|
+
readonly host: string;
|
|
19
|
+
}
|
|
14
20
|
export declare class AuditCollector {
|
|
15
21
|
private readonly dataPath;
|
|
16
22
|
private readonly settings;
|
|
23
|
+
private readonly projects;
|
|
17
24
|
private readonly log;
|
|
18
25
|
private pending;
|
|
19
26
|
private sessions;
|
|
27
|
+
private readonly book;
|
|
28
|
+
private gitRuns;
|
|
29
|
+
private held;
|
|
30
|
+
private readonly commitWatcher;
|
|
31
|
+
private readonly watched;
|
|
20
32
|
private watchers;
|
|
21
|
-
|
|
33
|
+
private maintainedOn;
|
|
34
|
+
constructor(dataPath: string, storageRoot: string, settings: AuditSettings, projects?: () => readonly AuditProject[]);
|
|
22
35
|
private add;
|
|
23
|
-
/**
|
|
36
|
+
/** Deletes old days, then starts watching the projects' files and commits. */
|
|
24
37
|
start(): void;
|
|
38
|
+
/** The projects not watched yet (one that joined the sandbox since): their files and their reflogs from now on. */
|
|
39
|
+
private watchProjects;
|
|
40
|
+
private watchFiles;
|
|
41
|
+
private linked;
|
|
25
42
|
/** Programs the sandbox reported: taken in (checked) and deleted. */
|
|
26
43
|
private takeGuestReports;
|
|
44
|
+
/** One line the sandbox wrote: only a program, with only the fields it may set. */
|
|
45
|
+
private takeLine;
|
|
46
|
+
private primaryProject;
|
|
27
47
|
private trackSessions;
|
|
48
|
+
/** New commits are held a moment; held ones whose time has come are written, linked to a session if one is known. */
|
|
49
|
+
private takeCommits;
|
|
50
|
+
/** File changes not linked yet: to the one session running in their project then, if there was exactly one. */
|
|
51
|
+
private linkFiles;
|
|
28
52
|
guard(detail: string): void;
|
|
53
|
+
/** Old days go when the watcher starts and again whenever the day changes while it runs. */
|
|
54
|
+
private maintainDaily;
|
|
29
55
|
/** Writes what came in since the last call (repeats folded); call every few seconds. */
|
|
30
|
-
flush(): void;
|
|
56
|
+
flush(final?: boolean): void;
|
|
31
57
|
stop(): void;
|
|
32
58
|
maintain(): void;
|
|
33
59
|
}
|
|
@@ -1,26 +1,68 @@
|
|
|
1
1
|
// What goes into the audit log, from the background watcher while the sandbox runs:
|
|
2
2
|
// - programs started in the sandbox: the .jsonl files it leaves in run\audit\ (untrusted: links refused, each file
|
|
3
|
-
// and line size-capped, every line checked and forced to kind "proc"
|
|
3
|
+
// and line size-capped, every line checked and forced to kind "proc", times kept to now, command lines scrubbed of
|
|
4
|
+
// secrets, a session it names kept only when this sandbox has that session), taken in and deleted;
|
|
4
5
|
// - files changed in the projects: watched from this PC (the sandbox cannot hide or fake these);
|
|
6
|
+
// - git commits in the projects: read from each project's reflog on this PC (git-commits.ts);
|
|
5
7
|
// - sessions starting and ending, and what the repo guard caught.
|
|
8
|
+
// Each event is linked to the agent session that did it where that can be known (attribute.ts), else to none.
|
|
6
9
|
import fs from 'node:fs';
|
|
7
10
|
import path from 'node:path';
|
|
8
11
|
import { z } from 'zod';
|
|
9
12
|
import { assertNoLinks } from '../../core/util/index.js';
|
|
10
13
|
import { RunDir } from '../../shared/channel/index.js';
|
|
11
14
|
import { JsonFile } from '../../shared/io/index.js';
|
|
15
|
+
import { hubName } from '../../shared/paths/index.js';
|
|
16
|
+
// The file itself, not the barrel: the barrel leads back to the watcher, which imports this file.
|
|
17
|
+
import { ProjectRegistry } from '../projects/registry.js';
|
|
12
18
|
import { hostStateFile } from '../storage/index.js';
|
|
13
|
-
import {
|
|
19
|
+
import { makesCommits, SessionBook } from './attribute.js';
|
|
20
|
+
import { CommitWatcher } from './git-commits.js';
|
|
21
|
+
import { AuditEventSchema, AuditLog, AuditSessionIdSchema, fold } from './log.js';
|
|
22
|
+
import { redactCommandLine } from './redact.js';
|
|
14
23
|
const MAX_FILES_PER_PASS = 200;
|
|
15
24
|
const MAX_FILE_BYTES = 8 * 1024 * 1024;
|
|
25
|
+
/** Most of the sandbox's reports taken in per pass, all files together. */
|
|
26
|
+
const MAX_BYTES_PER_PASS = 16 * 1024 * 1024;
|
|
27
|
+
/** Commits waiting to be written, and git commands remembered for linking them, at most. */
|
|
28
|
+
const MAX_HELD_COMMITS = 500;
|
|
29
|
+
const MAX_GIT_RUNS = 2000;
|
|
30
|
+
/** A plain file's text (null when it is not a plain file or is bigger than `max`), checked on the open handle. */
|
|
31
|
+
function readPlainFile(file, max) {
|
|
32
|
+
if (!fs.lstatSync(file).isFile())
|
|
33
|
+
return null;
|
|
34
|
+
const fd = fs.openSync(file, 'r');
|
|
35
|
+
try {
|
|
36
|
+
const stat = fs.fstatSync(fd);
|
|
37
|
+
if (!stat.isFile() || stat.size > max)
|
|
38
|
+
return null;
|
|
39
|
+
const buffer = Buffer.alloc(stat.size);
|
|
40
|
+
const read = fs.readSync(fd, buffer, 0, stat.size, 0);
|
|
41
|
+
return buffer.subarray(0, read).toString('utf8');
|
|
42
|
+
}
|
|
43
|
+
finally {
|
|
44
|
+
fs.closeSync(fd);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
16
47
|
/** Paths in a project not worth a line each (git's own bookkeeping, dependency folders). */
|
|
17
48
|
const QUIET = /(^|[\\/])(\.git[\\/](objects|logs|refs|index\.lock|FETCH_HEAD|ORIG_HEAD)|node_modules[\\/]|target[\\/]|\.venv[\\/]|__pycache__[\\/])/i;
|
|
49
|
+
/** A commit waits this long before it is written, so the git command that made it (reported later) can claim it. */
|
|
50
|
+
const COMMIT_HOLD_MS = 45 * 1000;
|
|
51
|
+
/** Git commands are remembered this long for linking commits. */
|
|
52
|
+
const GIT_RUNS_KEPT_MS = 10 * 60 * 1000;
|
|
53
|
+
/** A time the sandbox reports is believed only this far from now (else it is now). */
|
|
54
|
+
const PAST_MS = 2 * 86400000;
|
|
55
|
+
const FUTURE_MS = 3600000;
|
|
56
|
+
/** Projects watched at most (the running sandbox's registry can grow while it runs). */
|
|
57
|
+
const MAX_PROJECTS = 64;
|
|
18
58
|
const AuditSettingsSchema = z.object({
|
|
19
59
|
enabled: z.boolean(),
|
|
20
60
|
days: z.number().int().min(1).max(365),
|
|
21
61
|
maxMb: z.number().int().min(5).max(5000),
|
|
22
62
|
processes: z.boolean(),
|
|
23
63
|
files: z.boolean(),
|
|
64
|
+
/** Missing in files written by versions before commits were recorded. */
|
|
65
|
+
commits: z.boolean().default(true),
|
|
24
66
|
projects: z.array(z.string().max(1024)).max(64),
|
|
25
67
|
});
|
|
26
68
|
const settingsFile = (storageRoot, dataPath) => new JsonFile(AuditSettingsSchema, hostStateFile(storageRoot, dataPath, '-audit.json'));
|
|
@@ -31,51 +73,109 @@ export function saveAuditSettings(storageRoot, dataPath, settings) {
|
|
|
31
73
|
export function readAuditSettings(storageRoot, dataPath) {
|
|
32
74
|
return settingsFile(storageRoot, dataPath).read();
|
|
33
75
|
}
|
|
76
|
+
/** The sandbox's projects: the registry's (projects that joined while it runs too) and the ones it started with. */
|
|
77
|
+
function registryProjects(storageRoot, started) {
|
|
78
|
+
const out = [];
|
|
79
|
+
const add = (name, host) => {
|
|
80
|
+
if (!out.some((p) => p.host.toLowerCase() === host.toLowerCase()))
|
|
81
|
+
out.push({ name, host });
|
|
82
|
+
};
|
|
83
|
+
try {
|
|
84
|
+
for (const p of new ProjectRegistry(storageRoot).read()?.projects ?? [])
|
|
85
|
+
add(p.name, p.host);
|
|
86
|
+
}
|
|
87
|
+
catch {
|
|
88
|
+
// No registry (or an unreadable one): the projects it started with.
|
|
89
|
+
}
|
|
90
|
+
for (const host of started)
|
|
91
|
+
add(hubName(host, []), host);
|
|
92
|
+
return out.slice(0, MAX_PROJECTS);
|
|
93
|
+
}
|
|
94
|
+
/** A time from the sandbox as an ISO time near now: one too far off (a wrong clock, a made-up line) becomes now. */
|
|
95
|
+
function believable(t, now) {
|
|
96
|
+
const ms = Date.parse(t);
|
|
97
|
+
return Number.isNaN(ms) || ms < now - PAST_MS || ms > now + FUTURE_MS
|
|
98
|
+
? new Date(now).toISOString()
|
|
99
|
+
: new Date(ms).toISOString();
|
|
100
|
+
}
|
|
34
101
|
export class AuditCollector {
|
|
35
102
|
dataPath;
|
|
36
103
|
settings;
|
|
104
|
+
projects;
|
|
37
105
|
log;
|
|
38
106
|
pending = [];
|
|
39
107
|
sessions = new Map();
|
|
108
|
+
book = new SessionBook();
|
|
109
|
+
gitRuns = [];
|
|
110
|
+
held = [];
|
|
111
|
+
commitWatcher = new CommitWatcher();
|
|
112
|
+
watched = new Set();
|
|
40
113
|
watchers = [];
|
|
41
|
-
|
|
114
|
+
maintainedOn = '';
|
|
115
|
+
constructor(dataPath, storageRoot, settings, projects = () => registryProjects(storageRoot, settings.projects)) {
|
|
42
116
|
this.dataPath = dataPath;
|
|
43
117
|
this.settings = settings;
|
|
118
|
+
this.projects = projects;
|
|
44
119
|
this.log = new AuditLog(AuditLog.dirFor(storageRoot, dataPath), settings);
|
|
45
120
|
}
|
|
46
121
|
add(e) {
|
|
47
122
|
this.pending.push({ t: new Date().toISOString(), ...e });
|
|
48
123
|
}
|
|
49
|
-
/**
|
|
124
|
+
/** Deletes old days, then starts watching the projects' files and commits. */
|
|
50
125
|
start() {
|
|
126
|
+
this.maintainDaily();
|
|
51
127
|
this.add({
|
|
52
128
|
k: 'note',
|
|
53
129
|
p: 'sandbox started; audit: ' +
|
|
54
|
-
[
|
|
130
|
+
[
|
|
131
|
+
this.settings.processes && 'programs',
|
|
132
|
+
this.settings.files && 'files',
|
|
133
|
+
this.settings.commits && 'commits',
|
|
134
|
+
'sessions',
|
|
135
|
+
]
|
|
136
|
+
.filter(Boolean)
|
|
137
|
+
.join(', '),
|
|
55
138
|
});
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
139
|
+
this.watchProjects();
|
|
140
|
+
}
|
|
141
|
+
/** The projects not watched yet (one that joined the sandbox since): their files and their reflogs from now on. */
|
|
142
|
+
watchProjects() {
|
|
143
|
+
for (const project of this.projects()) {
|
|
144
|
+
const key = project.host.toLowerCase();
|
|
145
|
+
if (this.watched.has(key))
|
|
146
|
+
continue;
|
|
147
|
+
this.watched.add(key);
|
|
148
|
+
if (this.settings.commits)
|
|
149
|
+
this.commitWatcher.prime([project.host]);
|
|
150
|
+
if (this.settings.files)
|
|
151
|
+
this.watchFiles(project);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
watchFiles(project) {
|
|
155
|
+
try {
|
|
156
|
+
this.watchers.push(fs.watch(project.host, { recursive: true }, (event, file) => {
|
|
157
|
+
const rel = file ? String(file) : '';
|
|
158
|
+
if (!rel || QUIET.test(rel))
|
|
159
|
+
return;
|
|
160
|
+
this.add({
|
|
161
|
+
k: 'file',
|
|
162
|
+
a: event === 'rename' ? 'rename' : 'change',
|
|
163
|
+
p: rel.replace(/\\/g, '/').slice(0, 1024),
|
|
164
|
+
w: project.host,
|
|
165
|
+
pj: project.name,
|
|
166
|
+
});
|
|
167
|
+
}));
|
|
75
168
|
}
|
|
169
|
+
catch {
|
|
170
|
+
/* a project that is gone */
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
linked(link) {
|
|
174
|
+
const s = this.book.get(link?.s);
|
|
175
|
+
return link && s ? { s: s.id, ag: s.agent, pj: s.project, sv: link.sv } : {};
|
|
76
176
|
}
|
|
77
177
|
/** Programs the sandbox reported: taken in (checked) and deleted. */
|
|
78
|
-
takeGuestReports() {
|
|
178
|
+
takeGuestReports(now) {
|
|
79
179
|
if (!this.settings.processes)
|
|
80
180
|
return;
|
|
81
181
|
const dir = new RunDir(this.dataPath).auditDir;
|
|
@@ -91,38 +191,80 @@ export class AuditCollector {
|
|
|
91
191
|
catch {
|
|
92
192
|
return;
|
|
93
193
|
}
|
|
194
|
+
// A budget for the whole pass: what is over it waits (the next pass takes the next files).
|
|
195
|
+
let budget = MAX_BYTES_PER_PASS;
|
|
94
196
|
for (const name of names) {
|
|
95
197
|
const file = path.join(dir, name);
|
|
96
198
|
try {
|
|
97
|
-
const
|
|
98
|
-
if (
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
if (e.success)
|
|
111
|
-
this.pending.push({ ...e.data, k: 'proc', w: undefined, a: undefined });
|
|
112
|
-
}
|
|
199
|
+
const size = fs.lstatSync(file).size;
|
|
200
|
+
if (size <= MAX_FILE_BYTES && size > budget)
|
|
201
|
+
break;
|
|
202
|
+
}
|
|
203
|
+
catch {
|
|
204
|
+
continue;
|
|
205
|
+
}
|
|
206
|
+
try {
|
|
207
|
+
const text = readPlainFile(file, MAX_FILE_BYTES);
|
|
208
|
+
if (text !== null) {
|
|
209
|
+
budget -= text.length;
|
|
210
|
+
for (const line of text.split('\n'))
|
|
211
|
+
this.takeLine(line, now);
|
|
113
212
|
}
|
|
114
213
|
}
|
|
214
|
+
catch {
|
|
215
|
+
/* unreadable: deleted below like any other */
|
|
216
|
+
}
|
|
115
217
|
finally {
|
|
116
|
-
|
|
218
|
+
try {
|
|
219
|
+
fs.rmSync(file, { force: true, recursive: true });
|
|
220
|
+
}
|
|
221
|
+
catch {
|
|
222
|
+
// Cannot be removed (held open): the next pass tries again.
|
|
223
|
+
}
|
|
117
224
|
}
|
|
118
225
|
}
|
|
119
226
|
}
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
227
|
+
/** One line the sandbox wrote: only a program, with only the fields it may set. */
|
|
228
|
+
takeLine(line, now) {
|
|
229
|
+
if (!line.trim() || line.length > 8192)
|
|
230
|
+
return;
|
|
231
|
+
let raw;
|
|
232
|
+
try {
|
|
233
|
+
raw = JSON.parse(line);
|
|
234
|
+
}
|
|
235
|
+
catch {
|
|
236
|
+
return;
|
|
237
|
+
}
|
|
238
|
+
const e = AuditEventSchema.safeParse(raw);
|
|
239
|
+
if (!e.success)
|
|
240
|
+
return;
|
|
241
|
+
const { t, p, c, pp } = e.data;
|
|
242
|
+
const claimed = AuditSessionIdSchema.safeParse(e.data.s);
|
|
243
|
+
const session = claimed.success ? this.book.get(claimed.data) : undefined;
|
|
244
|
+
const event = {
|
|
245
|
+
t: believable(t, now),
|
|
246
|
+
k: 'proc',
|
|
247
|
+
...(p !== undefined ? { p } : {}),
|
|
248
|
+
...(c !== undefined ? { c: redactCommandLine(c) } : {}),
|
|
249
|
+
...(pp !== undefined ? { pp } : {}),
|
|
250
|
+
...(session ? this.linked({ s: session.id, sv: 'p' }) : {}),
|
|
251
|
+
};
|
|
252
|
+
if (session && makesCommits(p ?? '', c ?? '') && this.gitRuns.length < MAX_GIT_RUNS)
|
|
253
|
+
this.gitRuns.push({ t: Date.parse(event.t), session: session.id });
|
|
254
|
+
this.pending.push(event);
|
|
255
|
+
}
|
|
256
|
+
primaryProject() {
|
|
257
|
+
return this.projects()[0]?.name ?? 'project';
|
|
258
|
+
}
|
|
259
|
+
trackSessions(now) {
|
|
260
|
+
const list = new RunDir(this.dataPath).listSessions();
|
|
261
|
+
this.book.update(list, this.primaryProject(), now);
|
|
262
|
+
const phases = new Map(list.map((s) => [s.id, s.phase]));
|
|
263
|
+
for (const s of list) {
|
|
123
264
|
const before = this.sessions.get(s.id);
|
|
265
|
+
const who = this.linked({ s: s.id, sv: 'p' });
|
|
124
266
|
if (!before && s.phase === 'running')
|
|
125
|
-
this.add({ k: 'session', a: 'start', p: s.harness, w: s.cwd, c: s.id });
|
|
267
|
+
this.add({ k: 'session', a: 'start', p: s.harness, w: s.cwd, c: s.id, ...who, sv: undefined });
|
|
126
268
|
if (before === 'running' && (s.phase === 'exited' || s.phase === 'failed'))
|
|
127
269
|
this.add({
|
|
128
270
|
k: 'session',
|
|
@@ -130,35 +272,88 @@ export class AuditCollector {
|
|
|
130
272
|
p: s.harness,
|
|
131
273
|
w: s.cwd,
|
|
132
274
|
c: s.id + (s.exitCode !== undefined ? ' exit ' + s.exitCode : ''),
|
|
275
|
+
...who,
|
|
276
|
+
sv: undefined,
|
|
133
277
|
});
|
|
134
278
|
}
|
|
135
|
-
this.sessions =
|
|
279
|
+
this.sessions = phases;
|
|
280
|
+
}
|
|
281
|
+
/** New commits are held a moment; held ones whose time has come are written, linked to a session if one is known. */
|
|
282
|
+
takeCommits(now, final) {
|
|
283
|
+
if (!this.settings.commits)
|
|
284
|
+
return;
|
|
285
|
+
const projects = this.projects();
|
|
286
|
+
// Commits are never folded, so their number is capped here (a reflog full of made-up lines cannot flood the log).
|
|
287
|
+
const fresh = this.commitWatcher.commits(projects.map((p) => p.host));
|
|
288
|
+
const room = Math.max(0, MAX_HELD_COMMITS - this.held.length);
|
|
289
|
+
for (const commit of fresh.slice(0, room))
|
|
290
|
+
this.held.push({ commit, seenAt: now });
|
|
291
|
+
if (fresh.length > room)
|
|
292
|
+
this.add({ k: 'note', p: fresh.length - room + ' more commits were seen at once and not listed (audit cap)' });
|
|
293
|
+
const due = this.held.filter((h) => final || now - h.seenAt >= COMMIT_HOLD_MS);
|
|
294
|
+
this.held = this.held.filter((h) => !due.includes(h));
|
|
295
|
+
for (const { commit, seenAt } of due) {
|
|
296
|
+
const name = projects.find((p) => p.host.toLowerCase() === commit.project.toLowerCase())?.name ??
|
|
297
|
+
hubName(commit.project, []);
|
|
298
|
+
// Git's own time, unless the sandbox's clock (or the line) is far off: then when it was seen.
|
|
299
|
+
const t = believable(commit.t, seenAt);
|
|
300
|
+
this.pending.push({
|
|
301
|
+
t,
|
|
302
|
+
k: 'commit',
|
|
303
|
+
a: commit.action,
|
|
304
|
+
p: redactCommandLine(commit.message),
|
|
305
|
+
h: commit.sha,
|
|
306
|
+
au: commit.author,
|
|
307
|
+
w: commit.project,
|
|
308
|
+
pj: name,
|
|
309
|
+
// With programs recorded, a commit no session's git command made is not put on a session as "likely" (it
|
|
310
|
+
// may well be yours, made on this PC).
|
|
311
|
+
...this.linked(this.book.linkCommit(name, Date.parse(t), this.gitRuns, !this.settings.processes)),
|
|
312
|
+
});
|
|
313
|
+
}
|
|
314
|
+
this.gitRuns = this.gitRuns.filter((g) => g.t >= now - GIT_RUNS_KEPT_MS);
|
|
315
|
+
}
|
|
316
|
+
/** File changes not linked yet: to the one session running in their project then, if there was exactly one. */
|
|
317
|
+
linkFiles() {
|
|
318
|
+
this.pending = this.pending.map((e) => e.k === 'file' && !e.s && e.pj ? { ...e, ...this.linked(this.book.linkFile(e.pj, Date.parse(e.t))) } : e);
|
|
136
319
|
}
|
|
137
320
|
guard(detail) {
|
|
138
321
|
this.add({ k: 'guard', p: detail.slice(0, 1024) });
|
|
139
322
|
}
|
|
323
|
+
/** Old days go when the watcher starts and again whenever the day changes while it runs. */
|
|
324
|
+
maintainDaily() {
|
|
325
|
+
const today = new Date().toISOString().slice(0, 10);
|
|
326
|
+
if (today === this.maintainedOn)
|
|
327
|
+
return;
|
|
328
|
+
this.maintainedOn = today;
|
|
329
|
+
this.log.maintain(today);
|
|
330
|
+
}
|
|
140
331
|
/** Writes what came in since the last call (repeats folded); call every few seconds. */
|
|
141
|
-
flush() {
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
332
|
+
flush(final = false) {
|
|
333
|
+
const now = Date.now();
|
|
334
|
+
const step = (work) => {
|
|
335
|
+
try {
|
|
336
|
+
work();
|
|
337
|
+
}
|
|
338
|
+
catch {
|
|
339
|
+
/* next pass */
|
|
340
|
+
}
|
|
341
|
+
};
|
|
342
|
+
step(() => this.watchProjects());
|
|
343
|
+
// Sessions first: a program or commit can only be linked to a session the book knows.
|
|
344
|
+
step(() => this.trackSessions(now));
|
|
345
|
+
step(() => this.takeGuestReports(now));
|
|
346
|
+
step(() => this.takeCommits(now, final));
|
|
347
|
+
step(() => this.linkFiles());
|
|
154
348
|
const events = this.pending.sort((a, b) => a.t.localeCompare(b.t));
|
|
155
349
|
this.pending = [];
|
|
156
350
|
this.log.append(fold(events));
|
|
351
|
+
step(() => this.maintainDaily());
|
|
157
352
|
}
|
|
158
353
|
stop() {
|
|
159
354
|
this.watchers.forEach((w) => w.close());
|
|
160
355
|
this.add({ k: 'note', p: 'sandbox closed' });
|
|
161
|
-
this.flush();
|
|
356
|
+
this.flush(true);
|
|
162
357
|
this.log.maintain();
|
|
163
358
|
}
|
|
164
359
|
maintain() {
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/** One commit as the reflog records it. */
|
|
2
|
+
export interface CommitSeen {
|
|
3
|
+
/** ISO time git wrote. */
|
|
4
|
+
readonly t: string;
|
|
5
|
+
readonly sha: string;
|
|
6
|
+
/** Who made it, as git names them ("Name <email>"). */
|
|
7
|
+
readonly author: string;
|
|
8
|
+
/** commit | amend | merge | cherry-pick | revert | rebase | initial. */
|
|
9
|
+
readonly action: string;
|
|
10
|
+
/** The first line of its message. */
|
|
11
|
+
readonly message: string;
|
|
12
|
+
/** The project folder on this PC. */
|
|
13
|
+
readonly project: string;
|
|
14
|
+
}
|
|
15
|
+
/** A reflog line as a commit, or null when it is not one (a checkout, a reset, a pull without a merge commit ...). */
|
|
16
|
+
export declare function parseReflogLine(line: string, project: string): CommitSeen | null;
|
|
17
|
+
/** Follows the projects' reflogs: commits() returns the commits added since the last call. */
|
|
18
|
+
export declare class CommitWatcher {
|
|
19
|
+
/** Reflog file -> bytes already read. */
|
|
20
|
+
private readonly offsets;
|
|
21
|
+
/** The reflogs of a project: the main one and each worktree's. */
|
|
22
|
+
private reflogs;
|
|
23
|
+
/** Starts from what is there now: only commits made from here on are reported. */
|
|
24
|
+
prime(projects: readonly string[]): void;
|
|
25
|
+
private sizeOf;
|
|
26
|
+
/** The commits added to the projects' reflogs since the last call. A reflog seen for the first time is primed. */
|
|
27
|
+
commits(projects: readonly string[]): CommitSeen[];
|
|
28
|
+
private readRange;
|
|
29
|
+
}
|