pilotswarm-sdk 0.5.30 → 0.5.32

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.
Files changed (81) hide show
  1. package/api/index.d.ts +40 -0
  2. package/api/index.js +8 -0
  3. package/api/src/session-authz.d.ts +76 -0
  4. package/api/src/session-authz.js +240 -0
  5. package/dist/agent-fqn.d.ts +72 -0
  6. package/dist/agent-fqn.d.ts.map +1 -0
  7. package/dist/agent-fqn.js +137 -0
  8. package/dist/agent-fqn.js.map +1 -0
  9. package/dist/agent-loader.d.ts.map +1 -1
  10. package/dist/agent-loader.js +22 -6
  11. package/dist/agent-loader.js.map +1 -1
  12. package/dist/agent-manager-tools.d.ts +135 -0
  13. package/dist/agent-manager-tools.d.ts.map +1 -0
  14. package/dist/agent-manager-tools.js +813 -0
  15. package/dist/agent-manager-tools.js.map +1 -0
  16. package/dist/agent-package-diff.d.ts +97 -0
  17. package/dist/agent-package-diff.d.ts.map +1 -0
  18. package/dist/agent-package-diff.js +203 -0
  19. package/dist/agent-package-diff.js.map +1 -0
  20. package/dist/agent-package-format.d.ts +2 -0
  21. package/dist/agent-package-format.d.ts.map +1 -1
  22. package/dist/agent-package-format.js +15 -0
  23. package/dist/agent-package-format.js.map +1 -1
  24. package/dist/agent-package-import-fetch.d.ts +68 -0
  25. package/dist/agent-package-import-fetch.d.ts.map +1 -0
  26. package/dist/agent-package-import-fetch.js +156 -0
  27. package/dist/agent-package-import-fetch.js.map +1 -0
  28. package/dist/agent-package-import-policy.d.ts +133 -0
  29. package/dist/agent-package-import-policy.d.ts.map +1 -0
  30. package/dist/agent-package-import-policy.js +347 -0
  31. package/dist/agent-package-import-policy.js.map +1 -0
  32. package/dist/agent-package-installer.d.ts.map +1 -1
  33. package/dist/agent-package-installer.js +30 -4
  34. package/dist/agent-package-installer.js.map +1 -1
  35. package/dist/cms-migrations.d.ts.map +1 -1
  36. package/dist/cms-migrations.js +653 -1
  37. package/dist/cms-migrations.js.map +1 -1
  38. package/dist/cms.d.ts +103 -10
  39. package/dist/cms.d.ts.map +1 -1
  40. package/dist/cms.js +85 -10
  41. package/dist/cms.js.map +1 -1
  42. package/dist/index.d.ts +2 -1
  43. package/dist/index.d.ts.map +1 -1
  44. package/dist/index.js +1 -0
  45. package/dist/index.js.map +1 -1
  46. package/dist/inspect-tools.d.ts +68 -8
  47. package/dist/inspect-tools.d.ts.map +1 -1
  48. package/dist/inspect-tools.js +282 -21
  49. package/dist/inspect-tools.js.map +1 -1
  50. package/dist/managed-session.d.ts.map +1 -1
  51. package/dist/managed-session.js +87 -0
  52. package/dist/managed-session.js.map +1 -1
  53. package/dist/management-client.d.ts +20 -1
  54. package/dist/management-client.d.ts.map +1 -1
  55. package/dist/management-client.js +25 -0
  56. package/dist/management-client.js.map +1 -1
  57. package/dist/session-manager.d.ts +61 -0
  58. package/dist/session-manager.d.ts.map +1 -1
  59. package/dist/session-manager.js +143 -1
  60. package/dist/session-manager.js.map +1 -1
  61. package/dist/session-proxy.d.ts +9 -1
  62. package/dist/session-proxy.d.ts.map +1 -1
  63. package/dist/session-proxy.js +345 -12
  64. package/dist/session-proxy.js.map +1 -1
  65. package/dist/types.d.ts +31 -0
  66. package/dist/types.d.ts.map +1 -1
  67. package/dist/types.js.map +1 -1
  68. package/dist/web/web-management-client.d.ts +2 -0
  69. package/dist/web/web-management-client.d.ts.map +1 -1
  70. package/dist/web/web-management-client.js +6 -0
  71. package/dist/web/web-management-client.js.map +1 -1
  72. package/dist/worker.d.ts +5 -0
  73. package/dist/worker.d.ts.map +1 -1
  74. package/dist/worker.js +28 -0
  75. package/dist/worker.js.map +1 -1
  76. package/package.json +3 -3
  77. package/plugins/mgmt/agents/pilotswarm.agent.md +2 -2
  78. package/plugins/mgmt/skills/cost-latency-analysis/SKILL.md +1 -1
  79. package/plugins/mgmt/skills/graph-debug/SKILL.md +4 -4
  80. package/plugins/mgmt/skills/orchestration-session-lifecycle/SKILL.md +1 -1
  81. package/plugins/mgmt/agents/agent-tuner.agent.md +0 -316
@@ -0,0 +1,68 @@
1
+ /**
2
+ * The guarded fetch behind `import_agent_package`.
3
+ *
4
+ * Separated from the policy so the DECISIONS stay unit-testable without a
5
+ * network, and the I/O stays small enough to audit by eye.
6
+ *
7
+ * Three properties this file exists to guarantee, all from §15 A1:
8
+ *
9
+ * 1. **Every hop is re-checked.** An allowlisted origin that redirects to a
10
+ * disallowed one is the obvious bypass. `fetch()` follows redirects
11
+ * silently by default, so redirect following is done MANUALLY here with
12
+ * `redirect: "manual"` — automatic following would check only the first
13
+ * URL and fetch the last.
14
+ *
15
+ * 2. **DNS is not trusted.** The resolved address is checked against the
16
+ * private/metadata denylist on every hop, because an allowlisted NAME may
17
+ * still answer with `169.254.169.254`.
18
+ *
19
+ * 3. **Response bodies never reach an error, a log line or the transcript.**
20
+ * The agent reading this is the same agent an attacker may be talking to;
21
+ * echoing a body back is the exfiltration channel the allowlist exists to
22
+ * close.
23
+ *
24
+ * @module
25
+ */
26
+ import { IMPORT_FETCH_LIMITS, type ImportPolicy } from "./agent-package-import-policy.js";
27
+ export interface ImportFetchResult {
28
+ bytes: Buffer;
29
+ /** The final URL after any permitted redirects. */
30
+ finalUrl: string;
31
+ /** Each URL visited, in order — useful in an audit record. */
32
+ hops: string[];
33
+ }
34
+ export declare class ImportRefusedError extends Error {
35
+ readonly code = "AGENT_PACKAGE_IMPORT_REFUSED";
36
+ /** The URL that was refused, credentials already stripped. */
37
+ readonly url: string | undefined;
38
+ constructor(message: string, url?: string);
39
+ }
40
+ /**
41
+ * Resolve a hostname and refuse if ANY answer is in blocked space.
42
+ *
43
+ * "Any" rather than "the one we will use" on purpose: a name that resolves to
44
+ * both a public and a private address is a DNS-rebinding setup, and we have no
45
+ * way to pin which record the socket will actually take.
46
+ */
47
+ export declare function assertHostResolvesPublicly(hostname: string, resolver?: {
48
+ lookup: (h: string, opts: any) => Promise<Array<{
49
+ address: string;
50
+ }>>;
51
+ }): Promise<string[]>;
52
+ /**
53
+ * Fetch a package from an allowlisted origin, or refuse.
54
+ *
55
+ * `fetchImpl` and `resolver` are injectable so the guard logic can be tested
56
+ * against redirect chains and hostile DNS without a network.
57
+ */
58
+ export declare function guardedImportFetch(rawUrl: string, policy: ImportPolicy, opts?: {
59
+ fetchImpl?: typeof fetch;
60
+ resolver?: {
61
+ lookup: (h: string, o: any) => Promise<Array<{
62
+ address: string;
63
+ }>>;
64
+ };
65
+ limits?: typeof IMPORT_FETCH_LIMITS;
66
+ signal?: AbortSignal;
67
+ }): Promise<ImportFetchResult>;
68
+ //# sourceMappingURL=agent-package-import-fetch.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"agent-package-import-fetch.d.ts","sourceRoot":"","sources":["../src/agent-package-import-fetch.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAGH,OAAO,EACH,mBAAmB,EAGnB,KAAK,YAAY,EACpB,MAAM,kCAAkC,CAAC;AAE1C,MAAM,WAAW,iBAAiB;IAC9B,KAAK,EAAE,MAAM,CAAC;IACd,mDAAmD;IACnD,QAAQ,EAAE,MAAM,CAAC;IACjB,8DAA8D;IAC9D,IAAI,EAAE,MAAM,EAAE,CAAC;CAClB;AAED,qBAAa,kBAAmB,SAAQ,KAAK;IACzC,QAAQ,CAAC,IAAI,kCAAkC;IAC/C,8DAA8D;IAC9D,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,CAAC;gBACrB,OAAO,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,MAAM;CAK5C;AAED;;;;;;GAMG;AACH,wBAAsB,0BAA0B,CAC5C,QAAQ,EAAE,MAAM,EAChB,QAAQ,GAAE;IAAE,MAAM,EAAE,CAAC,CAAC,EAAE,MAAM,EAAE,IAAI,EAAE,GAAG,KAAK,OAAO,CAAC,KAAK,CAAC;QAAE,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC,CAAA;CAAe,GACjG,OAAO,CAAC,MAAM,EAAE,CAAC,CAqBnB;AAED;;;;;GAKG;AACH,wBAAsB,kBAAkB,CACpC,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,YAAY,EACpB,IAAI,GAAE;IACF,SAAS,CAAC,EAAE,OAAO,KAAK,CAAC;IACzB,QAAQ,CAAC,EAAE;QAAE,MAAM,EAAE,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,GAAG,KAAK,OAAO,CAAC,KAAK,CAAC;YAAE,OAAO,EAAE,MAAM,CAAA;SAAE,CAAC,CAAC,CAAA;KAAE,CAAC;IAClF,MAAM,CAAC,EAAE,OAAO,mBAAmB,CAAC;IACpC,MAAM,CAAC,EAAE,WAAW,CAAC;CACnB,GACP,OAAO,CAAC,iBAAiB,CAAC,CAiE5B"}
@@ -0,0 +1,156 @@
1
+ /**
2
+ * The guarded fetch behind `import_agent_package`.
3
+ *
4
+ * Separated from the policy so the DECISIONS stay unit-testable without a
5
+ * network, and the I/O stays small enough to audit by eye.
6
+ *
7
+ * Three properties this file exists to guarantee, all from §15 A1:
8
+ *
9
+ * 1. **Every hop is re-checked.** An allowlisted origin that redirects to a
10
+ * disallowed one is the obvious bypass. `fetch()` follows redirects
11
+ * silently by default, so redirect following is done MANUALLY here with
12
+ * `redirect: "manual"` — automatic following would check only the first
13
+ * URL and fetch the last.
14
+ *
15
+ * 2. **DNS is not trusted.** The resolved address is checked against the
16
+ * private/metadata denylist on every hop, because an allowlisted NAME may
17
+ * still answer with `169.254.169.254`.
18
+ *
19
+ * 3. **Response bodies never reach an error, a log line or the transcript.**
20
+ * The agent reading this is the same agent an attacker may be talking to;
21
+ * echoing a body back is the exfiltration channel the allowlist exists to
22
+ * close.
23
+ *
24
+ * @module
25
+ */
26
+ import * as dns from "node:dns/promises";
27
+ import { IMPORT_FETCH_LIMITS, checkImportUrl, isBlockedAddress, } from "./agent-package-import-policy.js";
28
+ export class ImportRefusedError extends Error {
29
+ code = "AGENT_PACKAGE_IMPORT_REFUSED";
30
+ /** The URL that was refused, credentials already stripped. */
31
+ url;
32
+ constructor(message, url) {
33
+ super(message);
34
+ this.name = "ImportRefusedError";
35
+ this.url = url;
36
+ }
37
+ }
38
+ /**
39
+ * Resolve a hostname and refuse if ANY answer is in blocked space.
40
+ *
41
+ * "Any" rather than "the one we will use" on purpose: a name that resolves to
42
+ * both a public and a private address is a DNS-rebinding setup, and we have no
43
+ * way to pin which record the socket will actually take.
44
+ */
45
+ export async function assertHostResolvesPublicly(hostname, resolver = dns) {
46
+ let answers;
47
+ try {
48
+ answers = await resolver.lookup(hostname, { all: true, verbatim: true });
49
+ }
50
+ catch (error) {
51
+ // A name that will not resolve cannot be fetched anyway; refusing here
52
+ // keeps the failure legible instead of surfacing a socket error later.
53
+ throw new ImportRefusedError(`could not resolve host "${hostname}": ${error?.code ?? "lookup failed"}`);
54
+ }
55
+ if (!answers?.length) {
56
+ throw new ImportRefusedError(`host "${hostname}" resolved to no addresses`);
57
+ }
58
+ const addresses = answers.map((a) => a.address);
59
+ for (const address of addresses) {
60
+ if (isBlockedAddress(address)) {
61
+ throw new ImportRefusedError(`host "${hostname}" resolves to a non-public address (${address}); refusing to connect`);
62
+ }
63
+ }
64
+ return addresses;
65
+ }
66
+ /**
67
+ * Fetch a package from an allowlisted origin, or refuse.
68
+ *
69
+ * `fetchImpl` and `resolver` are injectable so the guard logic can be tested
70
+ * against redirect chains and hostile DNS without a network.
71
+ */
72
+ export async function guardedImportFetch(rawUrl, policy, opts = {}) {
73
+ const limits = opts.limits ?? IMPORT_FETCH_LIMITS;
74
+ const doFetch = opts.fetchImpl ?? fetch;
75
+ const hops = [];
76
+ let current = rawUrl;
77
+ for (let hop = 0; hop <= limits.maxRedirects; hop += 1) {
78
+ // The SAME check for the initial URL and every redirect. Sharing one
79
+ // function is the point: a redirect path with its own weaker check is
80
+ // how this gets broken.
81
+ const decision = checkImportUrl(current, policy);
82
+ if (!decision.allowed) {
83
+ throw new ImportRefusedError(hop === 0
84
+ ? `import refused: ${decision.reason}`
85
+ : `import refused after ${hop} redirect(s): ${decision.reason}`, decision.url);
86
+ }
87
+ const url = new URL(decision.url);
88
+ await assertHostResolvesPublicly(url.hostname, opts.resolver);
89
+ hops.push(url.href);
90
+ const timeout = AbortSignal.timeout(limits.timeoutMs);
91
+ const signal = opts.signal
92
+ ? AbortSignal.any?.([timeout, opts.signal]) ?? timeout
93
+ : timeout;
94
+ const response = await doFetch(url.href, {
95
+ // MANUAL: automatic following would validate only the first URL
96
+ // and fetch whatever the last one turned out to be.
97
+ redirect: "manual",
98
+ signal,
99
+ headers: { accept: "application/octet-stream, application/gzip, */*" },
100
+ });
101
+ if (response.status >= 300 && response.status < 400) {
102
+ const location = response.headers.get("location");
103
+ if (!location) {
104
+ throw new ImportRefusedError(`redirect with no Location header (HTTP ${response.status})`, url.href);
105
+ }
106
+ // Relative Locations are legal; resolve against the current URL so
107
+ // the next iteration re-checks a fully-qualified target.
108
+ current = new URL(location, url.href).href;
109
+ continue;
110
+ }
111
+ if (!response.ok) {
112
+ // Status only. The body may be attacker-authored, and this string
113
+ // reaches an agent transcript.
114
+ throw new ImportRefusedError(`fetch failed with HTTP ${response.status}`, url.href);
115
+ }
116
+ const declared = Number(response.headers.get("content-length") ?? "");
117
+ if (Number.isFinite(declared) && declared > limits.maxBytes) {
118
+ throw new ImportRefusedError(`package is ${declared} bytes, over the ${limits.maxBytes}-byte import cap`, url.href);
119
+ }
120
+ const bytes = await readCapped(response, limits.maxBytes, url.href);
121
+ return { bytes, finalUrl: url.href, hops };
122
+ }
123
+ throw new ImportRefusedError(`too many redirects (limit ${limits.maxRedirects})`, hops[hops.length - 1]);
124
+ }
125
+ /**
126
+ * Read a body, stopping the moment it exceeds the cap.
127
+ *
128
+ * Streamed rather than buffered-then-measured: a declared Content-Length is
129
+ * the server's claim, and an unbounded `arrayBuffer()` on a lying server is a
130
+ * worker OOM.
131
+ */
132
+ async function readCapped(response, maxBytes, url) {
133
+ const body = response.body;
134
+ if (!body)
135
+ return Buffer.alloc(0);
136
+ const chunks = [];
137
+ let total = 0;
138
+ const reader = body.getReader();
139
+ for (;;) {
140
+ const { done, value } = await reader.read();
141
+ if (done)
142
+ break;
143
+ const chunk = Buffer.from(value);
144
+ total += chunk.length;
145
+ if (total > maxBytes) {
146
+ try {
147
+ await reader.cancel();
148
+ }
149
+ catch { /* already closing */ }
150
+ throw new ImportRefusedError(`package exceeds the ${maxBytes}-byte import cap`, url);
151
+ }
152
+ chunks.push(chunk);
153
+ }
154
+ return Buffer.concat(chunks);
155
+ }
156
+ //# sourceMappingURL=agent-package-import-fetch.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"agent-package-import-fetch.js","sourceRoot":"","sources":["../src/agent-package-import-fetch.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,133 @@
1
+ /**
2
+ * Import policy — the allowlist and network guards for `import_agent_package`.
3
+ *
4
+ * ── Why this file is defensive out of proportion to its size ────────────
5
+ *
6
+ * `import_agent_package` is the worker's FIRST outbound-fetch primitive
7
+ * (§15 A1). Before it, there was no `fetch(` anywhere in the worker core. It
8
+ * is reachable with a model-chosen URL, inside an agent whose entire job is
9
+ * reading untrusted material — session transcripts, archives, other people's
10
+ * packages. That is the lethal trifecta in one tool: private data, untrusted
11
+ * content, and an outbound channel.
12
+ *
13
+ * The primary control is an ALLOWLIST rather than a blocklist, because there
14
+ * is then no hostile URL to filter — anything off the list is simply
15
+ * unreachable. A prompt injection saying "also import from
16
+ * http://169.254.169.254/metadata/identity/oauth2/token" cannot be honoured,
17
+ * not because we recognized it, but because that origin was never permitted.
18
+ *
19
+ * Layered underneath, because an allowlist alone is not enough: DNS is not
20
+ * ours to trust, so an allowlisted NAME that resolves into private address
21
+ * space must still be refused — and re-checked on every redirect hop.
22
+ *
23
+ * ── Everything here is a pure decision ──────────────────────────────────
24
+ *
25
+ * No sockets are opened in this module. It decides; the caller connects. That
26
+ * keeps the whole bypass surface unit-testable without a network.
27
+ *
28
+ * @module
29
+ */
30
+ /**
31
+ * The base allowlist is a CONSTANT IN SOURCE, not a config default.
32
+ *
33
+ * Three entries because GitHub serves one repository from three hosts. Keeping
34
+ * them here rather than in the shipped config file means changing them shows
35
+ * up in a PR diff, and a deployment cannot silently drop them by overwriting a
36
+ * file it mounts.
37
+ */
38
+ export declare const BASE_IMPORT_ALLOWLIST: readonly string[];
39
+ export declare const IMPORT_CONFIG_FILENAME = ".agent_packages.json";
40
+ export interface ImportAllowlistEntry {
41
+ protocol: "https:";
42
+ /** Lower-cased, trailing dot stripped. */
43
+ host: string;
44
+ /** Explicit port, or null meaning "default only". */
45
+ port: string | null;
46
+ /** Normalized path prefix, always starting with `/`. */
47
+ pathPrefix: string;
48
+ /** The entry as written, for error messages. */
49
+ raw: string;
50
+ }
51
+ export interface ImportPolicy {
52
+ entries: ImportAllowlistEntry[];
53
+ mode: "append" | "replace";
54
+ /** Where the deployment config came from, for diagnostics. */
55
+ configPath: string | null;
56
+ }
57
+ export interface UrlDecision {
58
+ allowed: boolean;
59
+ /** Populated only when refused. Never contains a response body. */
60
+ reason?: string;
61
+ /** The normalized URL that was checked. */
62
+ url?: string;
63
+ }
64
+ /**
65
+ * Normalize a path for prefix comparison.
66
+ *
67
+ * Percent-decoding happens BEFORE `..` resolution, because otherwise
68
+ * `%2e%2e%2f` survives as literal text and slips past a traversal check that
69
+ * only looked for `../`.
70
+ */
71
+ export declare function normalizePathForMatch(rawPath: string): string;
72
+ /** Strip a single trailing dot and lower-case. `GitHub.COM.` → `github.com`. */
73
+ export declare function normalizeHost(host: string): string;
74
+ /**
75
+ * Parse one allowlist entry. Returns null (with no exception) for anything
76
+ * unusable — a malformed entry must never widen the list.
77
+ */
78
+ export declare function parseAllowlistEntry(raw: string): ImportAllowlistEntry | null;
79
+ /**
80
+ * Load `.agent_packages.json` and compose it with the base list.
81
+ *
82
+ * A MALFORMED FILE IS FATAL rather than ignored. A security control that
83
+ * silently falls back to a default when its config is broken is how
84
+ * deployments end up unknowingly running the permissive version — the operator
85
+ * edited the file, fat-fingered a comma, and never learned the edit did
86
+ * nothing.
87
+ */
88
+ export declare function loadImportPolicy(opts?: {
89
+ configDir?: string;
90
+ configPath?: string;
91
+ }): ImportPolicy;
92
+ /**
93
+ * Is this resolved address inside a range we must never fetch from?
94
+ *
95
+ * Applied to the RESOLVED ADDRESS, not the hostname, and on every redirect
96
+ * hop — an allowlisted name whose DNS answers `169.254.169.254` is exactly
97
+ * the attack this stops.
98
+ */
99
+ export declare function isBlockedAddress(address: string): boolean;
100
+ /**
101
+ * Reject host forms that are not plain names — decimal, octal and hex IPv4
102
+ * literals resolve to addresses without ever looking like one.
103
+ * `http://2130706433/` is `127.0.0.1`.
104
+ */
105
+ export declare function isSuspiciousHostLiteral(host: string): boolean;
106
+ /**
107
+ * May we fetch this URL?
108
+ *
109
+ * Call for the initial URL AND for every redirect hop — an allowed origin
110
+ * redirecting to a disallowed one is the obvious bypass, and the only thing
111
+ * that stops it is re-asking the same question about the new location.
112
+ */
113
+ export declare function checkImportUrl(rawUrl: string, policy: ImportPolicy): UrlDecision;
114
+ /**
115
+ * Prefix match on SEGMENT BOUNDARIES.
116
+ *
117
+ * `/affandar/pilotswarm-evil` must not match the prefix
118
+ * `/affandar/pilotswarm/`. A naive `startsWith` accepts it, which is how
119
+ * allowlists usually get broken.
120
+ */
121
+ export declare function pathPrefixMatches(requestPath: string, prefix: string): boolean;
122
+ /**
123
+ * Caps applied to every import fetch. Values are conservative on purpose:
124
+ * a package is a small tarball, and anything wanting more headroom than this
125
+ * is not the thing this tool exists to fetch.
126
+ */
127
+ export declare const IMPORT_FETCH_LIMITS: Readonly<{
128
+ /** Matches the documented upload cap so both paths refuse at the same size. */
129
+ maxBytes: number;
130
+ maxRedirects: 3;
131
+ timeoutMs: 20000;
132
+ }>;
133
+ //# sourceMappingURL=agent-package-import-policy.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"agent-package-import-policy.d.ts","sourceRoot":"","sources":["../src/agent-package-import-policy.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AAKH;;;;;;;GAOG;AACH,eAAO,MAAM,qBAAqB,EAAE,SAAS,MAAM,EAIjD,CAAC;AAEH,eAAO,MAAM,sBAAsB,yBAAyB,CAAC;AAE7D,MAAM,WAAW,oBAAoB;IACjC,QAAQ,EAAE,QAAQ,CAAC;IACnB,0CAA0C;IAC1C,IAAI,EAAE,MAAM,CAAC;IACb,qDAAqD;IACrD,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,wDAAwD;IACxD,UAAU,EAAE,MAAM,CAAC;IACnB,gDAAgD;IAChD,GAAG,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,YAAY;IACzB,OAAO,EAAE,oBAAoB,EAAE,CAAC;IAChC,IAAI,EAAE,QAAQ,GAAG,SAAS,CAAC;IAC3B,8DAA8D;IAC9D,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;CAC7B;AAED,MAAM,WAAW,WAAW;IACxB,OAAO,EAAE,OAAO,CAAC;IACjB,mEAAmE;IACnE,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,2CAA2C;IAC3C,GAAG,CAAC,EAAE,MAAM,CAAC;CAChB;AAID;;;;;;GAMG;AACH,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CA2B7D;AAED,gFAAgF;AAChF,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAElD;AAED;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,MAAM,GAAG,oBAAoB,GAAG,IAAI,CAmB5E;AAID;;;;;;;;GAQG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,GAAE;IAAE,SAAS,CAAC,EAAE,MAAM,CAAC;IAAC,UAAU,CAAC,EAAE,MAAM,CAAA;CAAO,GAAG,YAAY,CAmDrG;AAID;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAgCzD;AAED;;;;GAIG;AACH,wBAAgB,uBAAuB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAgB7D;AAID;;;;;;GAMG;AACH,wBAAgB,cAAc,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,YAAY,GAAG,WAAW,CA+ChF;AAED;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAAC,WAAW,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAK9E;AAgBD;;;;GAIG;AACH,eAAO,MAAM,mBAAmB;IAC5B,+EAA+E;;;;EAIjF,CAAC"}