@ory/argus 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (80) hide show
  1. package/README.md +134 -0
  2. package/assets/commands/local-down.md +19 -0
  3. package/assets/commands/local-up.md +27 -0
  4. package/assets/skills/auth-setup/SKILL.md +279 -0
  5. package/assets/skills/local-dev/SKILL.md +206 -0
  6. package/assets/skills/login-flow/SKILL.md +383 -0
  7. package/assets/skills/social-login/SKILL.md +312 -0
  8. package/dist/agent-auth.d.ts +204 -0
  9. package/dist/agent-auth.js +553 -0
  10. package/dist/auth-gate.d.ts +71 -0
  11. package/dist/auth-gate.js +308 -0
  12. package/dist/auth-store.d.ts +75 -0
  13. package/dist/auth-store.js +261 -0
  14. package/dist/auth.d.ts +93 -0
  15. package/dist/auth.js +323 -0
  16. package/dist/cli.d.ts +73 -0
  17. package/dist/cli.js +484 -0
  18. package/dist/client.d.ts +158 -0
  19. package/dist/client.js +679 -0
  20. package/dist/config.d.ts +135 -0
  21. package/dist/config.js +344 -0
  22. package/dist/denial.d.ts +79 -0
  23. package/dist/denial.js +103 -0
  24. package/dist/dev.d.ts +95 -0
  25. package/dist/dev.js +514 -0
  26. package/dist/index.d.ts +20 -0
  27. package/dist/index.js +137 -0
  28. package/dist/local/cli.d.ts +12 -0
  29. package/dist/local/cli.js +95 -0
  30. package/dist/local/configs.d.ts +89 -0
  31. package/dist/local/configs.js +634 -0
  32. package/dist/local/health.d.ts +32 -0
  33. package/dist/local/health.js +65 -0
  34. package/dist/local/index.d.ts +6 -0
  35. package/dist/local/index.js +38 -0
  36. package/dist/local/jaeger-main.d.ts +13 -0
  37. package/dist/local/jaeger-main.js +85 -0
  38. package/dist/local/jaeger.d.ts +50 -0
  39. package/dist/local/jaeger.js +162 -0
  40. package/dist/local/main.d.ts +7 -0
  41. package/dist/local/main.js +14 -0
  42. package/dist/local/manager.d.ts +45 -0
  43. package/dist/local/manager.js +676 -0
  44. package/dist/local/seed.d.ts +71 -0
  45. package/dist/local/seed.js +237 -0
  46. package/dist/logger.d.ts +29 -0
  47. package/dist/logger.js +139 -0
  48. package/dist/mcp.d.ts +76 -0
  49. package/dist/mcp.js +122 -0
  50. package/dist/otel/exporter.d.ts +17 -0
  51. package/dist/otel/exporter.js +12 -0
  52. package/dist/otel/index.d.ts +2 -0
  53. package/dist/otel/index.js +8 -0
  54. package/dist/otel/otlp-http.d.ts +116 -0
  55. package/dist/otel/otlp-http.js +322 -0
  56. package/dist/registry/cli.d.ts +12 -0
  57. package/dist/registry/cli.js +76 -0
  58. package/dist/registry/config.d.ts +23 -0
  59. package/dist/registry/config.js +80 -0
  60. package/dist/registry/index.d.ts +3 -0
  61. package/dist/registry/index.js +21 -0
  62. package/dist/registry/main.d.ts +7 -0
  63. package/dist/registry/main.js +14 -0
  64. package/dist/registry/manager.d.ts +38 -0
  65. package/dist/registry/manager.js +674 -0
  66. package/dist/setup.d.ts +118 -0
  67. package/dist/setup.js +398 -0
  68. package/dist/skills.d.ts +78 -0
  69. package/dist/skills.js +264 -0
  70. package/dist/subject.d.ts +43 -0
  71. package/dist/subject.js +55 -0
  72. package/dist/tool-metadata.d.ts +41 -0
  73. package/dist/tool-metadata.js +127 -0
  74. package/dist/tracer.d.ts +172 -0
  75. package/dist/tracer.js +452 -0
  76. package/dist/types.d.ts +57 -0
  77. package/dist/types.js +3 -0
  78. package/dist/watch-sandbox.d.ts +9 -0
  79. package/dist/watch-sandbox.js +81 -0
  80. package/package.json +79 -0
package/dist/skills.js ADDED
@@ -0,0 +1,264 @@
1
+ "use strict";
2
+ /**
3
+ * Canonical Ory agent skills and commands.
4
+ *
5
+ * The skill playbooks (auth-setup, login-flow, social-login, local-dev) and the
6
+ * local-stack commands (local-up, local-down) live once, as token-bearing
7
+ * templates under `packages/core/assets/`. Every harness plugin renders them
8
+ * through {@link renderOrySkills} / {@link renderOryCommands}, substituting the
9
+ * harness's CLI binary, package name, and the way it references sibling skills
10
+ * and commands. Plugins then write the rendered docs into whatever location
11
+ * their harness discovers skills/commands from (a marketplace plugin dir, an
12
+ * extension bundle, `~/.codex/skills`, `.opencode/skills`, …).
13
+ *
14
+ * This keeps the ~1200 lines of guidance in one place — there is no per-harness
15
+ * copy to drift.
16
+ */
17
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
18
+ if (k2 === undefined) k2 = k;
19
+ var desc = Object.getOwnPropertyDescriptor(m, k);
20
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
21
+ desc = { enumerable: true, get: function() { return m[k]; } };
22
+ }
23
+ Object.defineProperty(o, k2, desc);
24
+ }) : (function(o, m, k, k2) {
25
+ if (k2 === undefined) k2 = k;
26
+ o[k2] = m[k];
27
+ }));
28
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
29
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
30
+ }) : function(o, v) {
31
+ o["default"] = v;
32
+ });
33
+ var __importStar = (this && this.__importStar) || (function () {
34
+ var ownKeys = function(o) {
35
+ ownKeys = Object.getOwnPropertyNames || function (o) {
36
+ var ar = [];
37
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
38
+ return ar;
39
+ };
40
+ return ownKeys(o);
41
+ };
42
+ return function (mod) {
43
+ if (mod && mod.__esModule) return mod;
44
+ var result = {};
45
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
46
+ __setModuleDefault(result, mod);
47
+ return result;
48
+ };
49
+ })();
50
+ Object.defineProperty(exports, "__esModule", { value: true });
51
+ exports.ORY_COMMAND_SLUGS = exports.ORY_COMMAND_SKILL_NAMES = exports.ORY_SKILL_NAMES = void 0;
52
+ exports.renderOrySkills = renderOrySkills;
53
+ exports.renderOryCommands = renderOryCommands;
54
+ exports.toSkillMarkdown = toSkillMarkdown;
55
+ exports.commandToSkill = commandToSkill;
56
+ exports.commandToToml = commandToToml;
57
+ exports.commandToFrontmatterMarkdown = commandToFrontmatterMarkdown;
58
+ exports.commandToPlainMarkdown = commandToPlainMarkdown;
59
+ exports.writeSkillTree = writeSkillTree;
60
+ exports.removeSkillDirs = removeSkillDirs;
61
+ const fs = __importStar(require("node:fs"));
62
+ const path = __importStar(require("node:path"));
63
+ const SKILL_SOURCES = [
64
+ { id: "auth-setup", name: "ory-auth-setup", file: "skills/auth-setup/SKILL.md" },
65
+ { id: "login-flow", name: "ory-login-flow", file: "skills/login-flow/SKILL.md" },
66
+ { id: "social-login", name: "ory-social-login", file: "skills/social-login/SKILL.md" },
67
+ { id: "local-dev", name: "ory-local-dev", file: "skills/local-dev/SKILL.md" },
68
+ ];
69
+ const COMMAND_SOURCES = [
70
+ {
71
+ id: "local-up",
72
+ name: "ory-local-up",
73
+ slug: "local-up",
74
+ description: "Start the local Ory dev stack (Kratos, Keto, Hydra, gateway) in Docker and seed test data.",
75
+ file: "commands/local-up.md",
76
+ },
77
+ {
78
+ id: "local-down",
79
+ name: "ory-local-down",
80
+ slug: "local-down",
81
+ description: "Stop the local Ory dev stack, preserving data volumes.",
82
+ file: "commands/local-down.md",
83
+ },
84
+ ];
85
+ /** Names of the skills materialized by the plugins (for uninstall cleanup). */
86
+ exports.ORY_SKILL_NAMES = SKILL_SOURCES.map((s) => s.name);
87
+ /** Skill names of the local-stack commands when rendered as skills. */
88
+ exports.ORY_COMMAND_SKILL_NAMES = COMMAND_SOURCES.map((c) => c.name);
89
+ /** File stems of the local-stack commands when rendered as commands. */
90
+ exports.ORY_COMMAND_SLUGS = COMMAND_SOURCES.map((c) => c.slug);
91
+ /** Backtick-wrap an inline code reference. */
92
+ function code(value) {
93
+ return "`" + value + "`";
94
+ }
95
+ /**
96
+ * Build the token substitution map for a harness. The reference style differs:
97
+ *
98
+ * - Claude Code addresses sibling skills as `/project:<name>` and the commands
99
+ * as `/ory:local-up`.
100
+ * - Gemini CLI and OpenCode expose the commands as real slash commands
101
+ * (`/ory:local-up`) but model-invoke skills by name.
102
+ * - Codex and OpenClaw render the commands as user-invocable skills, so both
103
+ * skills and commands are referenced by their bare skill name.
104
+ */
105
+ function buildProfile(harness, opts) {
106
+ const skillRef = (name) => harness === "claude-code" ? code(`/project:${name}`) : code(name);
107
+ let localUp;
108
+ let localDown;
109
+ switch (harness) {
110
+ case "claude-code":
111
+ case "gemini-cli":
112
+ case "opencode":
113
+ localUp = code("/ory:local-up");
114
+ localDown = code("/ory:local-down");
115
+ break;
116
+ case "codex":
117
+ case "openclaw":
118
+ default:
119
+ localUp = code("ory-local-up");
120
+ localDown = code("ory-local-down");
121
+ break;
122
+ }
123
+ return {
124
+ binName: opts.binName,
125
+ packageName: opts.packageName,
126
+ tokens: {
127
+ "{{BIN}}": opts.binName,
128
+ "{{PKG}}": opts.packageName,
129
+ "{{REF_AUTH_SETUP}}": skillRef("ory-auth-setup"),
130
+ "{{REF_LOGIN_FLOW}}": skillRef("ory-login-flow"),
131
+ "{{REF_SOCIAL_LOGIN}}": skillRef("ory-social-login"),
132
+ "{{REF_LOCAL_DEV}}": skillRef("ory-local-dev"),
133
+ "{{REF_LOCAL_UP}}": localUp,
134
+ "{{REF_LOCAL_DOWN}}": localDown,
135
+ },
136
+ };
137
+ }
138
+ // ─── Template loading + substitution ────────────────────────────────
139
+ /**
140
+ * Resolve the assets root. The compiled module lives at `dist/skills.js` and
141
+ * tests run it from `src/skills.ts`; in both cases the `assets/` tree sits one
142
+ * level up at the package root. Published consumers get `assets/` via the
143
+ * package `files` allowlist.
144
+ */
145
+ function getAssetsDir() {
146
+ const candidate = path.join(__dirname, "..", "assets");
147
+ if (fs.existsSync(path.join(candidate, "skills")))
148
+ return candidate;
149
+ throw new Error(`Ory skill assets not found at ${candidate}. The @ory/argus package must ship its 'assets' directory.`);
150
+ }
151
+ function applyTokens(template, tokens) {
152
+ let out = template;
153
+ for (const [token, value] of Object.entries(tokens)) {
154
+ out = out.split(token).join(value);
155
+ }
156
+ return out;
157
+ }
158
+ /** Parse the minimal `name` / `description` frontmatter from a SKILL.md. */
159
+ function parseFrontmatter(markdown) {
160
+ const match = /^---\n([\s\S]*?)\n---\n?/.exec(markdown);
161
+ if (!match) {
162
+ return { name: "", description: "", body: markdown };
163
+ }
164
+ const block = match[1];
165
+ const body = markdown.slice(match[0].length).replace(/^\n+/, "");
166
+ const read = (key) => {
167
+ const m = new RegExp(`^${key}:\\s*(.*)$`, "m").exec(block);
168
+ return m ? m[1].trim() : "";
169
+ };
170
+ return { name: read("name"), description: read("description"), body };
171
+ }
172
+ // ─── Public render API ──────────────────────────────────────────────
173
+ /**
174
+ * Render the four guide skills for a harness as `SKILL.md` documents.
175
+ */
176
+ function renderOrySkills(harness, opts) {
177
+ const profile = buildProfile(harness, opts);
178
+ const assets = getAssetsDir();
179
+ return SKILL_SOURCES.map((src) => {
180
+ const template = fs.readFileSync(path.join(assets, src.file), "utf-8");
181
+ const markdown = applyTokens(template, profile.tokens);
182
+ const { name, description, body } = parseFrontmatter(markdown);
183
+ return {
184
+ id: src.id,
185
+ name: name || src.name,
186
+ description,
187
+ body,
188
+ markdown,
189
+ };
190
+ });
191
+ }
192
+ /**
193
+ * Render the two local-stack commands for a harness. The `body` is the rendered
194
+ * markdown; callers format it into the harness's command shape (plain markdown,
195
+ * TOML, frontmatter markdown) or wrap it as a skill via {@link commandToSkill}.
196
+ */
197
+ function renderOryCommands(harness, opts) {
198
+ const profile = buildProfile(harness, opts);
199
+ const assets = getAssetsDir();
200
+ return COMMAND_SOURCES.map((src) => {
201
+ const template = fs.readFileSync(path.join(assets, src.file), "utf-8");
202
+ return {
203
+ id: src.id,
204
+ name: src.name,
205
+ slug: src.slug,
206
+ description: src.description,
207
+ body: applyTokens(template, profile.tokens),
208
+ };
209
+ });
210
+ }
211
+ // ─── Format helpers ─────────────────────────────────────────────────
212
+ /** Compose a `SKILL.md` from its parts. */
213
+ function toSkillMarkdown(name, description, body) {
214
+ const trimmedBody = body.replace(/^\n+/, "").replace(/\s+$/, "");
215
+ return `---\nname: ${name}\ndescription: ${description}\n---\n\n${trimmedBody}\n`;
216
+ }
217
+ /** Wrap a rendered command as a user-invocable skill (Codex, OpenClaw). */
218
+ function commandToSkill(cmd) {
219
+ const markdown = toSkillMarkdown(cmd.name, cmd.description, cmd.body);
220
+ const { body } = parseFrontmatter(markdown);
221
+ return { id: cmd.id, name: cmd.name, description: cmd.description, body, markdown };
222
+ }
223
+ function tomlBasicString(value) {
224
+ return '"' + value.replace(/\\/g, "\\\\").replace(/"/g, '\\"') + '"';
225
+ }
226
+ /** Render a command as a Gemini CLI TOML command definition. */
227
+ function commandToToml(cmd) {
228
+ const body = cmd.body.replace(/\s+$/, "");
229
+ // TOML multiline literal strings ('''…''') don't process escapes, which is
230
+ // safe for markdown bodies (they contain backticks and quotes, never ''').
231
+ return (`# Generated from @ory/argus command templates — do not edit by hand.\n` +
232
+ `description = ${tomlBasicString(cmd.description)}\n` +
233
+ `prompt = '''\n${body}\n'''\n`);
234
+ }
235
+ /** Render a command as an OpenCode markdown command (frontmatter + body). */
236
+ function commandToFrontmatterMarkdown(cmd) {
237
+ const body = cmd.body.replace(/^\n+/, "").replace(/\s+$/, "");
238
+ return `---\ndescription: ${cmd.description}\n---\n\n${body}\n`;
239
+ }
240
+ /** Render a command as a plain markdown command file (Claude Code). */
241
+ function commandToPlainMarkdown(cmd) {
242
+ return cmd.body.replace(/\s+$/, "") + "\n";
243
+ }
244
+ // ─── Writers ────────────────────────────────────────────────────────
245
+ /**
246
+ * Write each skill into `<skillsDir>/<name>/SKILL.md`. Overwrites existing
247
+ * files; creates directories as needed.
248
+ */
249
+ function writeSkillTree(skillsDir, skills) {
250
+ for (const skill of skills) {
251
+ const dir = path.join(skillsDir, skill.name);
252
+ fs.mkdirSync(dir, { recursive: true });
253
+ fs.writeFileSync(path.join(dir, "SKILL.md"), skill.markdown);
254
+ }
255
+ }
256
+ /** Remove the named skill directories from `skillsDir` (best-effort). */
257
+ function removeSkillDirs(skillsDir, names) {
258
+ for (const name of names) {
259
+ const dir = path.join(skillsDir, name);
260
+ if (fs.existsSync(dir)) {
261
+ fs.rmSync(dir, { recursive: true, force: true });
262
+ }
263
+ }
264
+ }
@@ -0,0 +1,43 @@
1
+ /**
2
+ * User-subject resolution for permission checks.
3
+ *
4
+ * Permission checks need a subject reference that matches the form
5
+ * stored in Keto. Two shapes are supported:
6
+ *
7
+ * - **SubjectSet** — `<namespace>:<object>` (e.g. `User:<UUID>`). This
8
+ * is the form the Console's *Add relationship* dialog writes, so
9
+ * dev/demo flows that grant tuples through the UI use it.
10
+ *
11
+ * - **Direct SubjectID** — opaque string (e.g. `user:<UUID>` or
12
+ * `session:<id>`). Backward-compatible default when no subject
13
+ * namespace is configured.
14
+ *
15
+ * The selection is driven by `ORY_USER_SUBJECT_NAMESPACE`: when set, the
16
+ * resolver builds a SubjectSet using that namespace; when unset, it
17
+ * falls back to the direct SubjectID chain.
18
+ */
19
+ import type { OryAgentClient } from "./client.js";
20
+ export type UserSubjectRef = {
21
+ subjectId: string;
22
+ } | {
23
+ subjectSet: {
24
+ namespace: string;
25
+ object: string;
26
+ relation: string;
27
+ };
28
+ };
29
+ /**
30
+ * Resolve the user subject for permission checks. Prefers the auth-gate's
31
+ * `userPrincipal.subject`, falls back to `ORY_USER_SUBJECT_ID`, then the
32
+ * legacy `ORY_AGENT_SUBJECT_ID`, then the caller-supplied `fallback`
33
+ * (typically `session:<id>`).
34
+ *
35
+ * Returns a SubjectSet when `ORY_USER_SUBJECT_NAMESPACE` is set and a
36
+ * concrete subject is available; otherwise a direct SubjectID.
37
+ */
38
+ export declare function resolveUserSubject(client: OryAgentClient, fallback?: string): UserSubjectRef;
39
+ /**
40
+ * Printable label for a `UserSubjectRef`. Used in denial messages and
41
+ * span attributes. SubjectSets render as `<namespace>:<object>`.
42
+ */
43
+ export declare function subjectLabel(ref: UserSubjectRef): string;
@@ -0,0 +1,55 @@
1
+ "use strict";
2
+ /**
3
+ * User-subject resolution for permission checks.
4
+ *
5
+ * Permission checks need a subject reference that matches the form
6
+ * stored in Keto. Two shapes are supported:
7
+ *
8
+ * - **SubjectSet** — `<namespace>:<object>` (e.g. `User:<UUID>`). This
9
+ * is the form the Console's *Add relationship* dialog writes, so
10
+ * dev/demo flows that grant tuples through the UI use it.
11
+ *
12
+ * - **Direct SubjectID** — opaque string (e.g. `user:<UUID>` or
13
+ * `session:<id>`). Backward-compatible default when no subject
14
+ * namespace is configured.
15
+ *
16
+ * The selection is driven by `ORY_USER_SUBJECT_NAMESPACE`: when set, the
17
+ * resolver builds a SubjectSet using that namespace; when unset, it
18
+ * falls back to the direct SubjectID chain.
19
+ */
20
+ Object.defineProperty(exports, "__esModule", { value: true });
21
+ exports.resolveUserSubject = resolveUserSubject;
22
+ exports.subjectLabel = subjectLabel;
23
+ /**
24
+ * Resolve the user subject for permission checks. Prefers the auth-gate's
25
+ * `userPrincipal.subject`, falls back to `ORY_USER_SUBJECT_ID`, then the
26
+ * legacy `ORY_AGENT_SUBJECT_ID`, then the caller-supplied `fallback`
27
+ * (typically `session:<id>`).
28
+ *
29
+ * Returns a SubjectSet when `ORY_USER_SUBJECT_NAMESPACE` is set and a
30
+ * concrete subject is available; otherwise a direct SubjectID.
31
+ */
32
+ function resolveUserSubject(client, fallback) {
33
+ const subject = client.userPrincipal.subject
34
+ ?? process.env.ORY_USER_SUBJECT_ID
35
+ ?? process.env.ORY_AGENT_SUBJECT_ID
36
+ ?? fallback;
37
+ const ns = process.env.ORY_USER_SUBJECT_NAMESPACE;
38
+ if (subject && ns) {
39
+ return {
40
+ subjectSet: { namespace: ns, object: subject, relation: "" },
41
+ };
42
+ }
43
+ if (subject)
44
+ return { subjectId: subject };
45
+ return { subjectId: "agent:unknown" };
46
+ }
47
+ /**
48
+ * Printable label for a `UserSubjectRef`. Used in denial messages and
49
+ * span attributes. SubjectSets render as `<namespace>:<object>`.
50
+ */
51
+ function subjectLabel(ref) {
52
+ return "subjectSet" in ref
53
+ ? `${ref.subjectSet.namespace}:${ref.subjectSet.object}`
54
+ : ref.subjectId;
55
+ }
@@ -0,0 +1,41 @@
1
+ /**
2
+ * Privacy-preserving summarizers for tool input/output.
3
+ *
4
+ * Plugins must NEVER record raw tool content (commands, file bodies,
5
+ * URLs with query strings, prompts) directly into trace attributes.
6
+ * These helpers emit only:
7
+ * - byte size (proxy for "how much data")
8
+ * - sha256-truncated content hash (for dedup / "we've seen this before")
9
+ * - a small set of safe per-tool fields (exit code, file path, URL host)
10
+ *
11
+ * Anything that could reflect content goes through here so the redaction
12
+ * policy lives in one place.
13
+ */
14
+ export interface ToolInputSummary {
15
+ inputBytes?: number;
16
+ inputHash?: string;
17
+ /** Bash/shell — not the command itself. */
18
+ bashCommandBytes?: number;
19
+ bashCommandHash?: string;
20
+ /** File ops — path is metadata, file *contents* are not captured. */
21
+ filePath?: string;
22
+ /** Network ops — host only, never path or query string. */
23
+ urlHost?: string;
24
+ }
25
+ export interface ToolOutputSummary {
26
+ outputBytes?: number;
27
+ outputHash?: string;
28
+ /** Bash/shell exit code. */
29
+ bashExitCode?: number;
30
+ bashStdoutBytes?: number;
31
+ bashStderrBytes?: number;
32
+ }
33
+ /**
34
+ * Summarize a tool input value. `input` is whatever the harness passed —
35
+ * usually an object like `{ command: "..." }` or `{ file_path: "..." }`.
36
+ */
37
+ export declare function summarizeToolInput(toolName: string, input: unknown): ToolInputSummary;
38
+ /**
39
+ * Summarize a tool output/response value.
40
+ */
41
+ export declare function summarizeToolOutput(toolName: string, output: unknown): ToolOutputSummary;
@@ -0,0 +1,127 @@
1
+ "use strict";
2
+ /**
3
+ * Privacy-preserving summarizers for tool input/output.
4
+ *
5
+ * Plugins must NEVER record raw tool content (commands, file bodies,
6
+ * URLs with query strings, prompts) directly into trace attributes.
7
+ * These helpers emit only:
8
+ * - byte size (proxy for "how much data")
9
+ * - sha256-truncated content hash (for dedup / "we've seen this before")
10
+ * - a small set of safe per-tool fields (exit code, file path, URL host)
11
+ *
12
+ * Anything that could reflect content goes through here so the redaction
13
+ * policy lives in one place.
14
+ */
15
+ Object.defineProperty(exports, "__esModule", { value: true });
16
+ exports.summarizeToolInput = summarizeToolInput;
17
+ exports.summarizeToolOutput = summarizeToolOutput;
18
+ const node_crypto_1 = require("node:crypto");
19
+ const HASH_PREFIX_LEN = 16;
20
+ /**
21
+ * Summarize a tool input value. `input` is whatever the harness passed —
22
+ * usually an object like `{ command: "..." }` or `{ file_path: "..." }`.
23
+ */
24
+ function summarizeToolInput(toolName, input) {
25
+ if (input === undefined || input === null)
26
+ return {};
27
+ const result = {};
28
+ const serialized = stableSerialize(input);
29
+ if (serialized !== undefined) {
30
+ result.inputBytes = Buffer.byteLength(serialized, "utf8");
31
+ result.inputHash = sha256Prefix(serialized);
32
+ }
33
+ if (typeof input === "object") {
34
+ const obj = input;
35
+ // Bash / shell tools: capture command size + hash, never the command itself.
36
+ const command = pickString(obj, ["command", "cmd"]);
37
+ if (command !== undefined && isBashLikeTool(toolName)) {
38
+ result.bashCommandBytes = Buffer.byteLength(command, "utf8");
39
+ result.bashCommandHash = sha256Prefix(command);
40
+ }
41
+ // File ops: path is metadata. We do NOT capture file content/body fields
42
+ // (e.g. `content`, `new_string`, `text`, `body`).
43
+ const filePath = pickString(obj, ["file_path", "filePath", "path"]);
44
+ if (filePath !== undefined) {
45
+ result.filePath = filePath;
46
+ }
47
+ // Network/fetch tools: extract host only, drop path + query string.
48
+ const url = pickString(obj, ["url", "URL", "endpoint"]);
49
+ if (url !== undefined) {
50
+ const host = extractHost(url);
51
+ if (host)
52
+ result.urlHost = host;
53
+ }
54
+ }
55
+ return result;
56
+ }
57
+ /**
58
+ * Summarize a tool output/response value.
59
+ */
60
+ function summarizeToolOutput(toolName, output) {
61
+ if (output === undefined || output === null)
62
+ return {};
63
+ const result = {};
64
+ const serialized = stableSerialize(output);
65
+ if (serialized !== undefined) {
66
+ result.outputBytes = Buffer.byteLength(serialized, "utf8");
67
+ result.outputHash = sha256Prefix(serialized);
68
+ }
69
+ if (typeof output === "object") {
70
+ const obj = output;
71
+ if (isBashLikeTool(toolName)) {
72
+ const exit = obj.exitCode ?? obj.exit_code ?? obj.exit;
73
+ if (typeof exit === "number")
74
+ result.bashExitCode = exit;
75
+ const stdout = pickString(obj, ["stdout"]);
76
+ if (stdout !== undefined) {
77
+ result.bashStdoutBytes = Buffer.byteLength(stdout, "utf8");
78
+ }
79
+ const stderr = pickString(obj, ["stderr"]);
80
+ if (stderr !== undefined) {
81
+ result.bashStderrBytes = Buffer.byteLength(stderr, "utf8");
82
+ }
83
+ }
84
+ }
85
+ return result;
86
+ }
87
+ // ─── Helpers ──────────────────────────────────────────────────────
88
+ function stableSerialize(v) {
89
+ if (typeof v === "string")
90
+ return v;
91
+ try {
92
+ return JSON.stringify(v);
93
+ }
94
+ catch {
95
+ return undefined;
96
+ }
97
+ }
98
+ function sha256Prefix(s) {
99
+ return (0, node_crypto_1.createHash)("sha256").update(s).digest("hex").slice(0, HASH_PREFIX_LEN);
100
+ }
101
+ function pickString(obj, keys) {
102
+ for (const k of keys) {
103
+ const v = obj[k];
104
+ if (typeof v === "string" && v.length > 0)
105
+ return v;
106
+ }
107
+ return undefined;
108
+ }
109
+ function isBashLikeTool(toolName) {
110
+ const lower = toolName.toLowerCase();
111
+ return (lower === "bash" ||
112
+ lower === "shell" ||
113
+ lower === "exec" ||
114
+ lower === "execute" ||
115
+ lower === "command" ||
116
+ lower === "run");
117
+ }
118
+ function extractHost(url) {
119
+ try {
120
+ return new URL(url).host;
121
+ }
122
+ catch {
123
+ // Some agents pass `host:port/path` without scheme. Try to recover the host.
124
+ const match = /^([^/?#:]+)(?::\d+)?/.exec(url);
125
+ return match ? match[1] : undefined;
126
+ }
127
+ }