@warlock.js/ai-workspace 4.5.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 (78) hide show
  1. package/CHANGELOG.md +28 -0
  2. package/LICENSE +21 -0
  3. package/README.md +149 -0
  4. package/cjs/index.cjs +1609 -0
  5. package/cjs/index.cjs.map +1 -0
  6. package/esm/backends/local.d.mts +22 -0
  7. package/esm/backends/local.d.mts.map +1 -0
  8. package/esm/backends/local.mjs +208 -0
  9. package/esm/backends/local.mjs.map +1 -0
  10. package/esm/backends/mock.d.mts +62 -0
  11. package/esm/backends/mock.d.mts.map +1 -0
  12. package/esm/backends/mock.mjs +167 -0
  13. package/esm/backends/mock.mjs.map +1 -0
  14. package/esm/contracts/index.d.mts +5 -0
  15. package/esm/contracts/tool-io.type.d.mts +149 -0
  16. package/esm/contracts/tool-io.type.d.mts.map +1 -0
  17. package/esm/contracts/workspace-backend.contract.d.mts +69 -0
  18. package/esm/contracts/workspace-backend.contract.d.mts.map +1 -0
  19. package/esm/contracts/workspace-ops.contract.d.mts +72 -0
  20. package/esm/contracts/workspace-ops.contract.d.mts.map +1 -0
  21. package/esm/contracts/workspace-policy.type.d.mts +86 -0
  22. package/esm/contracts/workspace-policy.type.d.mts.map +1 -0
  23. package/esm/contracts/workspace.contract.d.mts +131 -0
  24. package/esm/contracts/workspace.contract.d.mts.map +1 -0
  25. package/esm/errors.d.mts +100 -0
  26. package/esm/errors.d.mts.map +1 -0
  27. package/esm/errors.mjs +58 -0
  28. package/esm/errors.mjs.map +1 -0
  29. package/esm/index.d.mts +20 -0
  30. package/esm/index.mjs +15 -0
  31. package/esm/ops.d.mts +25 -0
  32. package/esm/ops.d.mts.map +1 -0
  33. package/esm/ops.mjs +294 -0
  34. package/esm/ops.mjs.map +1 -0
  35. package/esm/policy/policy.d.mts +71 -0
  36. package/esm/policy/policy.d.mts.map +1 -0
  37. package/esm/policy/policy.mjs +184 -0
  38. package/esm/policy/policy.mjs.map +1 -0
  39. package/esm/tools/edit-file.d.mts +40 -0
  40. package/esm/tools/edit-file.d.mts.map +1 -0
  41. package/esm/tools/edit-file.mjs +57 -0
  42. package/esm/tools/edit-file.mjs.map +1 -0
  43. package/esm/tools/glob.d.mts +37 -0
  44. package/esm/tools/glob.d.mts.map +1 -0
  45. package/esm/tools/glob.mjs +45 -0
  46. package/esm/tools/glob.mjs.map +1 -0
  47. package/esm/tools/grep.d.mts +36 -0
  48. package/esm/tools/grep.d.mts.map +1 -0
  49. package/esm/tools/grep.mjs +51 -0
  50. package/esm/tools/grep.mjs.map +1 -0
  51. package/esm/tools/read-file.d.mts +35 -0
  52. package/esm/tools/read-file.d.mts.map +1 -0
  53. package/esm/tools/read-file.mjs +64 -0
  54. package/esm/tools/read-file.mjs.map +1 -0
  55. package/esm/tools/run-shell.d.mts +35 -0
  56. package/esm/tools/run-shell.d.mts.map +1 -0
  57. package/esm/tools/run-shell.mjs +65 -0
  58. package/esm/tools/run-shell.mjs.map +1 -0
  59. package/esm/tools/run-tests.d.mts +40 -0
  60. package/esm/tools/run-tests.d.mts.map +1 -0
  61. package/esm/tools/run-tests.mjs +67 -0
  62. package/esm/tools/run-tests.mjs.map +1 -0
  63. package/esm/tools/schema.mjs +111 -0
  64. package/esm/tools/schema.mjs.map +1 -0
  65. package/esm/tools/write-file.d.mts +33 -0
  66. package/esm/tools/write-file.d.mts.map +1 -0
  67. package/esm/tools/write-file.mjs +52 -0
  68. package/esm/tools/write-file.mjs.map +1 -0
  69. package/esm/workspace.d.mts +54 -0
  70. package/esm/workspace.d.mts.map +1 -0
  71. package/esm/workspace.mjs +210 -0
  72. package/esm/workspace.mjs.map +1 -0
  73. package/llms-full.txt +231 -0
  74. package/llms.txt +10 -0
  75. package/package.json +42 -0
  76. package/skills/README.md +13 -0
  77. package/skills/build-loop-agent/SKILL.md +100 -0
  78. package/skills/use-a-workspace/SKILL.md +117 -0
@@ -0,0 +1,111 @@
1
+ //#region ../@warlock.js/ai-workspace/src/tools/schema.ts
2
+ /**
3
+ * Tiny, dependency-free [Standard Schema](https://standardschema.dev)
4
+ * builders for the workspace tools' input validation. The package pins
5
+ * only `@warlock.js/ai` and `@warlock.js/fs` as runtime dependencies, so
6
+ * rather than pull in a schema library we hand-roll the few shapes the
7
+ * file tools need — exactly the pattern `@warlock.js/ai`'s own `tool()`
8
+ * tests use. Each builder returns a `StandardSchemaV1`, which is what
9
+ * `tool({ input })` validates against before calling `execute`.
10
+ *
11
+ * These intentionally cover only the primitive cases the FILE tools
12
+ * require (`string`, `optional string`, `optional number`, `optional
13
+ * boolean`, and an `object` of fields). They are not a general-purpose
14
+ * validator.
15
+ */
16
+ /** The vendor tag stamped on every issue these builders produce. */
17
+ const VENDOR = "ai-workspace";
18
+ /** Required string field — rejects anything that is not a string. */
19
+ function stringField() {
20
+ return (value, key) => {
21
+ if (typeof value === "string") return { value };
22
+ return { issues: [{
23
+ message: `"${key}" must be a string`,
24
+ path: [key]
25
+ }] };
26
+ };
27
+ }
28
+ /**
29
+ * Optional string field — accepts `undefined` (the property absent or
30
+ * explicitly undefined) or a string, and rejects every other type.
31
+ */
32
+ function optionalStringField() {
33
+ return (value, key) => {
34
+ if (value === void 0) return { value: void 0 };
35
+ if (typeof value === "string") return { value };
36
+ return { issues: [{
37
+ message: `"${key}" must be a string when provided`,
38
+ path: [key]
39
+ }] };
40
+ };
41
+ }
42
+ /**
43
+ * Optional finite-number field — accepts `undefined` or a finite number,
44
+ * rejecting `NaN`/`Infinity` and non-number types.
45
+ */
46
+ function optionalNumberField() {
47
+ return (value, key) => {
48
+ if (value === void 0) return { value: void 0 };
49
+ if (typeof value === "number" && Number.isFinite(value)) return { value };
50
+ return { issues: [{
51
+ message: `"${key}" must be a finite number when provided`,
52
+ path: [key]
53
+ }] };
54
+ };
55
+ }
56
+ /** Optional boolean field — accepts `undefined` or a boolean. */
57
+ function optionalBooleanField() {
58
+ return (value, key) => {
59
+ if (value === void 0) return { value: void 0 };
60
+ if (typeof value === "boolean") return { value };
61
+ return { issues: [{
62
+ message: `"${key}" must be a boolean when provided`,
63
+ path: [key]
64
+ }] };
65
+ };
66
+ }
67
+ /**
68
+ * Build a {@link StandardSchemaV1} for a flat object whose every property
69
+ * is validated by a {@link FieldValidator}. The input must be a non-null
70
+ * object; each declared field is validated and the (possibly coerced)
71
+ * values are collected into the typed result. All field issues are merged
72
+ * so the caller sees every problem at once.
73
+ *
74
+ * `T` is constrained to `object` rather than `Record<string, unknown>` so
75
+ * the tool IO `interface`s (which carry no implicit string index
76
+ * signature) satisfy it directly — only the declared keys in `shape` are
77
+ * ever read, so a string index signature is never required.
78
+ *
79
+ * @example
80
+ * const schema = objectSchema<{ path: string; limit?: number }>({
81
+ * path: stringField(),
82
+ * limit: optionalNumberField(),
83
+ * });
84
+ */
85
+ function objectSchema(shape) {
86
+ return { "~standard": {
87
+ version: 1,
88
+ vendor: VENDOR,
89
+ validate(input) {
90
+ if (typeof input !== "object" || input === null || Array.isArray(input)) return { issues: [{ message: "input must be an object" }] };
91
+ const source = input;
92
+ const issues = [];
93
+ const result = {};
94
+ for (const key of Object.keys(shape)) {
95
+ const field = shape[key];
96
+ const outcome = field(source[key], key);
97
+ if ("issues" in outcome) {
98
+ issues.push(...outcome.issues);
99
+ continue;
100
+ }
101
+ if (outcome.value !== void 0) result[key] = outcome.value;
102
+ }
103
+ if (issues.length > 0) return { issues };
104
+ return { value: result };
105
+ }
106
+ } };
107
+ }
108
+
109
+ //#endregion
110
+ export { objectSchema, optionalBooleanField, optionalNumberField, optionalStringField, stringField };
111
+ //# sourceMappingURL=schema.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"schema.mjs","names":[],"sources":["../../../../../../../@warlock.js/ai-workspace/src/tools/schema.ts"],"sourcesContent":["import type { StandardSchemaV1 } from \"@standard-schema/spec\";\n\n/**\n * Tiny, dependency-free [Standard Schema](https://standardschema.dev)\n * builders for the workspace tools' input validation. The package pins\n * only `@warlock.js/ai` and `@warlock.js/fs` as runtime dependencies, so\n * rather than pull in a schema library we hand-roll the few shapes the\n * file tools need — exactly the pattern `@warlock.js/ai`'s own `tool()`\n * tests use. Each builder returns a `StandardSchemaV1`, which is what\n * `tool({ input })` validates against before calling `execute`.\n *\n * These intentionally cover only the primitive cases the FILE tools\n * require (`string`, `optional string`, `optional number`, `optional\n * boolean`, and an `object` of fields). They are not a general-purpose\n * validator.\n */\n\n/** The vendor tag stamped on every issue these builders produce. */\nconst VENDOR = \"ai-workspace\";\n\n/**\n * A single field validator inside {@link objectSchema}: given a value,\n * return either the coerced value or a list of issues. Field validators\n * receive the raw property and the property name (for issue messages).\n */\ntype FieldValidator<T> = (\n value: unknown,\n key: string,\n) => { value: T } | { issues: StandardSchemaV1.Issue[] };\n\n/** Required string field — rejects anything that is not a string. */\nexport function stringField(): FieldValidator<string> {\n return (value, key) => {\n if (typeof value === \"string\") {\n return { value };\n }\n\n return { issues: [{ message: `\"${key}\" must be a string`, path: [key] }] };\n };\n}\n\n/**\n * Optional string field — accepts `undefined` (the property absent or\n * explicitly undefined) or a string, and rejects every other type.\n */\nexport function optionalStringField(): FieldValidator<string | undefined> {\n return (value, key) => {\n if (value === undefined) {\n return { value: undefined };\n }\n\n if (typeof value === \"string\") {\n return { value };\n }\n\n return {\n issues: [{ message: `\"${key}\" must be a string when provided`, path: [key] }],\n };\n };\n}\n\n/**\n * Optional finite-number field — accepts `undefined` or a finite number,\n * rejecting `NaN`/`Infinity` and non-number types.\n */\nexport function optionalNumberField(): FieldValidator<number | undefined> {\n return (value, key) => {\n if (value === undefined) {\n return { value: undefined };\n }\n\n if (typeof value === \"number\" && Number.isFinite(value)) {\n return { value };\n }\n\n return {\n issues: [\n { message: `\"${key}\" must be a finite number when provided`, path: [key] },\n ],\n };\n };\n}\n\n/** Optional boolean field — accepts `undefined` or a boolean. */\nexport function optionalBooleanField(): FieldValidator<boolean | undefined> {\n return (value, key) => {\n if (value === undefined) {\n return { value: undefined };\n }\n\n if (typeof value === \"boolean\") {\n return { value };\n }\n\n return {\n issues: [{ message: `\"${key}\" must be a boolean when provided`, path: [key] }],\n };\n };\n}\n\n/** The per-key field validator map describing an object schema's shape. */\ntype ObjectShape<T> = {\n [K in keyof T]-?: FieldValidator<T[K]>;\n};\n\n/**\n * Build a {@link StandardSchemaV1} for a flat object whose every property\n * is validated by a {@link FieldValidator}. The input must be a non-null\n * object; each declared field is validated and the (possibly coerced)\n * values are collected into the typed result. All field issues are merged\n * so the caller sees every problem at once.\n *\n * `T` is constrained to `object` rather than `Record<string, unknown>` so\n * the tool IO `interface`s (which carry no implicit string index\n * signature) satisfy it directly — only the declared keys in `shape` are\n * ever read, so a string index signature is never required.\n *\n * @example\n * const schema = objectSchema<{ path: string; limit?: number }>({\n * path: stringField(),\n * limit: optionalNumberField(),\n * });\n */\nexport function objectSchema<T extends object>(\n shape: ObjectShape<T>,\n): StandardSchemaV1<T> {\n return {\n \"~standard\": {\n version: 1,\n vendor: VENDOR,\n validate(input) {\n if (typeof input !== \"object\" || input === null || Array.isArray(input)) {\n return { issues: [{ message: \"input must be an object\" }] };\n }\n\n const source = input as Record<string, unknown>;\n const issues: StandardSchemaV1.Issue[] = [];\n const result: Record<string, unknown> = {};\n\n for (const key of Object.keys(shape) as (keyof T)[]) {\n const field = shape[key];\n const outcome = field(source[key as string], key as string);\n\n if (\"issues\" in outcome) {\n issues.push(...outcome.issues);\n\n continue;\n }\n\n // Only carry through keys that resolved to a defined value, so\n // optional-absent fields stay absent rather than becoming\n // explicit `undefined` properties.\n if (outcome.value !== undefined) {\n result[key as string] = outcome.value;\n }\n }\n\n if (issues.length > 0) {\n return { issues };\n }\n\n return { value: result as T };\n },\n },\n };\n}\n"],"mappings":";;;;;;;;;;;;;;;;AAkBA,MAAM,SAAS;;AAaf,SAAgB,cAAsC;CACpD,QAAQ,OAAO,QAAQ;EACrB,IAAI,OAAO,UAAU,UACnB,OAAO,EAAE,MAAM;EAGjB,OAAO,EAAE,QAAQ,CAAC;GAAE,SAAS,IAAI,IAAI;GAAqB,MAAM,CAAC,GAAG;EAAE,CAAC,EAAE;CAC3E;AACF;;;;;AAMA,SAAgB,sBAA0D;CACxE,QAAQ,OAAO,QAAQ;EACrB,IAAI,UAAU,QACZ,OAAO,EAAE,OAAO,OAAU;EAG5B,IAAI,OAAO,UAAU,UACnB,OAAO,EAAE,MAAM;EAGjB,OAAO,EACL,QAAQ,CAAC;GAAE,SAAS,IAAI,IAAI;GAAmC,MAAM,CAAC,GAAG;EAAE,CAAC,EAC9E;CACF;AACF;;;;;AAMA,SAAgB,sBAA0D;CACxE,QAAQ,OAAO,QAAQ;EACrB,IAAI,UAAU,QACZ,OAAO,EAAE,OAAO,OAAU;EAG5B,IAAI,OAAO,UAAU,YAAY,OAAO,SAAS,KAAK,GACpD,OAAO,EAAE,MAAM;EAGjB,OAAO,EACL,QAAQ,CACN;GAAE,SAAS,IAAI,IAAI;GAA0C,MAAM,CAAC,GAAG;EAAE,CAC3E,EACF;CACF;AACF;;AAGA,SAAgB,uBAA4D;CAC1E,QAAQ,OAAO,QAAQ;EACrB,IAAI,UAAU,QACZ,OAAO,EAAE,OAAO,OAAU;EAG5B,IAAI,OAAO,UAAU,WACnB,OAAO,EAAE,MAAM;EAGjB,OAAO,EACL,QAAQ,CAAC;GAAE,SAAS,IAAI,IAAI;GAAoC,MAAM,CAAC,GAAG;EAAE,CAAC,EAC/E;CACF;AACF;;;;;;;;;;;;;;;;;;;AAyBA,SAAgB,aACd,OACqB;CACrB,OAAO,EACL,aAAa;EACX,SAAS;EACT,QAAQ;EACR,SAAS,OAAO;GACd,IAAI,OAAO,UAAU,YAAY,UAAU,QAAQ,MAAM,QAAQ,KAAK,GACpE,OAAO,EAAE,QAAQ,CAAC,EAAE,SAAS,0BAA0B,CAAC,EAAE;GAG5D,MAAM,SAAS;GACf,MAAM,SAAmC,CAAC;GAC1C,MAAM,SAAkC,CAAC;GAEzC,KAAK,MAAM,OAAO,OAAO,KAAK,KAAK,GAAkB;IACnD,MAAM,QAAQ,MAAM;IACpB,MAAM,UAAU,MAAM,OAAO,MAAgB,GAAa;IAE1D,IAAI,YAAY,SAAS;KACvB,OAAO,KAAK,GAAG,QAAQ,MAAM;KAE7B;IACF;IAKA,IAAI,QAAQ,UAAU,QACpB,OAAO,OAAiB,QAAQ;GAEpC;GAEA,IAAI,OAAO,SAAS,GAClB,OAAO,EAAE,OAAO;GAGlB,OAAO,EAAE,OAAO,OAAY;EAC9B;CACF,EACF;AACF"}
@@ -0,0 +1,33 @@
1
+ import { WriteFileInput, WriteFileResult } from "../contracts/tool-io.type.mjs";
2
+ import { WorkspaceOps } from "../contracts/workspace-ops.contract.mjs";
3
+ import { ToolContract } from "@warlock.js/ai";
4
+
5
+ //#region ../@warlock.js/ai-workspace/src/tools/write-file.d.ts
6
+ /**
7
+ * Build the agent-facing `write_file` tool over a workspace's policy-
8
+ * enforced {@link WorkspaceOps}.
9
+ *
10
+ * The tool validates `{ path, content }` against a Standard Schema, then
11
+ * delegates to `ops.writeFile`, which atomically writes the full content
12
+ * (creating parent directories) and returns the byte count and content
13
+ * `hash`. The tool re-attaches the workspace-relative `path` so the
14
+ * result matches the {@link WriteFileResult} wire shape.
15
+ *
16
+ * **Errors flow as data.** A jail escape is thrown by `ops`; the
17
+ * `tool()` wrapper catches it and surfaces it in the returned `{ error }`
18
+ * field — `invoke()` never throws.
19
+ *
20
+ * @param ops - The shared, policy-enforced operation layer.
21
+ * @param options - Optional overrides; `name` renames the LLM-visible tool.
22
+ *
23
+ * @example
24
+ * const writeTool = makeWriteFileTool(ops);
25
+ * const { data } = await writeTool.invoke({ path: "src/new.ts", content: "export {};" });
26
+ * console.log(data.bytesWritten, data.hash);
27
+ */
28
+ declare function makeWriteFileTool(ops: WorkspaceOps, options?: {
29
+ name?: string;
30
+ }): ToolContract<WriteFileInput, WriteFileResult>;
31
+ //#endregion
32
+ export { makeWriteFileTool };
33
+ //# sourceMappingURL=write-file.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"write-file.d.mts","names":[],"sources":["../../../../../../../@warlock.js/ai-workspace/src/tools/write-file.ts"],"mappings":";;;;;;;;;AAmCA;;;;;;;;;;;;;;;;;AAG+C;iBAH/B,iBAAA,CACd,GAAA,EAAK,YAAA,EACL,OAAA;EAAY,IAAA;AAAA,IACX,YAAA,CAAa,cAAA,EAAgB,eAAA"}
@@ -0,0 +1,52 @@
1
+ import { objectSchema, stringField } from "./schema.mjs";
2
+ import { tool } from "@warlock.js/ai";
3
+
4
+ //#region ../@warlock.js/ai-workspace/src/tools/write-file.ts
5
+ /** Default tool name exposed to the LLM. */
6
+ const DEFAULT_NAME = "write_file";
7
+ /** Input schema for the `write_file` tool. */
8
+ const inputSchema = objectSchema({
9
+ path: stringField(),
10
+ content: stringField()
11
+ });
12
+ /**
13
+ * Build the agent-facing `write_file` tool over a workspace's policy-
14
+ * enforced {@link WorkspaceOps}.
15
+ *
16
+ * The tool validates `{ path, content }` against a Standard Schema, then
17
+ * delegates to `ops.writeFile`, which atomically writes the full content
18
+ * (creating parent directories) and returns the byte count and content
19
+ * `hash`. The tool re-attaches the workspace-relative `path` so the
20
+ * result matches the {@link WriteFileResult} wire shape.
21
+ *
22
+ * **Errors flow as data.** A jail escape is thrown by `ops`; the
23
+ * `tool()` wrapper catches it and surfaces it in the returned `{ error }`
24
+ * field — `invoke()` never throws.
25
+ *
26
+ * @param ops - The shared, policy-enforced operation layer.
27
+ * @param options - Optional overrides; `name` renames the LLM-visible tool.
28
+ *
29
+ * @example
30
+ * const writeTool = makeWriteFileTool(ops);
31
+ * const { data } = await writeTool.invoke({ path: "src/new.ts", content: "export {};" });
32
+ * console.log(data.bytesWritten, data.hash);
33
+ */
34
+ function makeWriteFileTool(ops, options) {
35
+ return tool({
36
+ name: options?.name ?? DEFAULT_NAME,
37
+ description: "Write full content to a workspace file, creating it (and any parent directories) if absent and overwriting it otherwise. The write is atomic. Returns the bytes written and the new content hash.",
38
+ input: inputSchema,
39
+ async execute(input) {
40
+ const { hash, bytesWritten } = await ops.writeFile(input.path, input.content);
41
+ return {
42
+ path: input.path,
43
+ bytesWritten,
44
+ hash
45
+ };
46
+ }
47
+ });
48
+ }
49
+
50
+ //#endregion
51
+ export { makeWriteFileTool };
52
+ //# sourceMappingURL=write-file.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"write-file.mjs","names":[],"sources":["../../../../../../../@warlock.js/ai-workspace/src/tools/write-file.ts"],"sourcesContent":["import { tool, type ToolContract } from \"@warlock.js/ai\";\nimport { objectSchema, stringField } from \"./schema\";\nimport type { WorkspaceOps, WriteFileInput, WriteFileResult } from \"../contracts\";\n\n/** Default tool name exposed to the LLM. */\nconst DEFAULT_NAME = \"write_file\";\n\n/** Input schema for the `write_file` tool. */\nconst inputSchema = objectSchema<WriteFileInput>({\n path: stringField(),\n content: stringField(),\n});\n\n/**\n * Build the agent-facing `write_file` tool over a workspace's policy-\n * enforced {@link WorkspaceOps}.\n *\n * The tool validates `{ path, content }` against a Standard Schema, then\n * delegates to `ops.writeFile`, which atomically writes the full content\n * (creating parent directories) and returns the byte count and content\n * `hash`. The tool re-attaches the workspace-relative `path` so the\n * result matches the {@link WriteFileResult} wire shape.\n *\n * **Errors flow as data.** A jail escape is thrown by `ops`; the\n * `tool()` wrapper catches it and surfaces it in the returned `{ error }`\n * field — `invoke()` never throws.\n *\n * @param ops - The shared, policy-enforced operation layer.\n * @param options - Optional overrides; `name` renames the LLM-visible tool.\n *\n * @example\n * const writeTool = makeWriteFileTool(ops);\n * const { data } = await writeTool.invoke({ path: \"src/new.ts\", content: \"export {};\" });\n * console.log(data.bytesWritten, data.hash);\n */\nexport function makeWriteFileTool(\n ops: WorkspaceOps,\n options?: { name?: string },\n): ToolContract<WriteFileInput, WriteFileResult> {\n return tool<WriteFileInput, WriteFileResult>({\n name: options?.name ?? DEFAULT_NAME,\n description:\n \"Write full content to a workspace file, creating it (and any parent \" +\n \"directories) if absent and overwriting it otherwise. The write is \" +\n \"atomic. Returns the bytes written and the new content hash.\",\n input: inputSchema,\n async execute(input) {\n const { hash, bytesWritten } = await ops.writeFile(input.path, input.content);\n\n return { path: input.path, bytesWritten, hash };\n },\n });\n}\n"],"mappings":";;;;;AAKA,MAAM,eAAe;;AAGrB,MAAM,cAAc,aAA6B;CAC/C,MAAM,YAAY;CAClB,SAAS,YAAY;AACvB,CAAC;;;;;;;;;;;;;;;;;;;;;;;AAwBD,SAAgB,kBACd,KACA,SAC+C;CAC/C,OAAO,KAAsC;EAC3C,MAAM,SAAS,QAAQ;EACvB,aACE;EAGF,OAAO;EACP,MAAM,QAAQ,OAAO;GACnB,MAAM,EAAE,MAAM,iBAAiB,MAAM,IAAI,UAAU,MAAM,MAAM,MAAM,OAAO;GAE5E,OAAO;IAAE,MAAM,MAAM;IAAM;IAAc;GAAK;EAChD;CACF,CAAC;AACH"}
@@ -0,0 +1,54 @@
1
+ import { WorkspacePolicy } from "./contracts/workspace-policy.type.mjs";
2
+ import { Workspace } from "./contracts/workspace.contract.mjs";
3
+ //#region ../@warlock.js/ai-workspace/src/workspace.d.ts
4
+ /**
5
+ * Build a {@link Workspace} — the integrator that wires a
6
+ * {@link WorkspacePolicy} to a backend, the shared policy-enforced ops
7
+ * layer, and the seven agent-facing tool factories.
8
+ *
9
+ * The backend is chosen from `policy.backend`: `"mock"` runs in memory
10
+ * (hermetic tests); the `"local"` default (and any absent value) runs
11
+ * over the real disk via `@warlock.js/fs` + `node:child_process`. The
12
+ * returned workspace exposes:
13
+ *
14
+ * - **`tools.*`** — `readFile` / `editFile` / `writeFile` / `runShell` /
15
+ * `runTests` / `grep` / `glob`, plus `all()` (every tool) and
16
+ * `pick(...)` (a least-privilege subset).
17
+ * - **direct methods** — `readFile` / `writeFile` / `editFile` / `exec` /
18
+ * `grep` / `glob` / `exists` / `mkdir` / `remove`, each delegating 1:1
19
+ * to the shared ops layer.
20
+ * - **`readonly()`** — a projection that vends only read/grep/glob and
21
+ * rejects every mutating direct method.
22
+ * - **`scope(subdir)`** — a sub-jailed workspace rooted at `subdir`.
23
+ *
24
+ * Available at runtime as `ai.workspace(policy)` once this module is
25
+ * imported (it registers the verb on the shared `ai` object).
26
+ *
27
+ * @param policy - The policy bounding the workspace (its `cwd` is the jail root).
28
+ * @returns A fully-wired {@link Workspace}.
29
+ *
30
+ * @example
31
+ * const ws = workspace({ cwd: "/srv/acme-api", shell: { allow: ["npm"], inheritEnv: ["PATH"] } });
32
+ * const dev = ai.agent({ model, tools: ws.tools.all() });
33
+ * await dev.execute("Make the failing cart-total suite green.");
34
+ *
35
+ * @example
36
+ * // Least-privilege reviewer — no write, no shell.
37
+ * const reviewer = ai.agent({ model, tools: ws.readonly().tools.all() });
38
+ */
39
+ declare function workspace(policy: WorkspacePolicy): Workspace;
40
+ /**
41
+ * Attach the `workspace` verb to the `ai` namespace via module augmentation,
42
+ * per the `ai.`-namespace convention. `@warlock.js/ai` now exposes a named `Ai`
43
+ * interface for exactly this, so after a bare `import "@warlock.js/ai-workspace"`,
44
+ * `ai.workspace(...)` is globally typed — no view/cast needed.
45
+ */
46
+ declare module "@warlock.js/ai" {
47
+ interface Ai {
48
+ /** Build a policy-jailed filesystem + shell {@link Workspace}. */
49
+ workspace(policy: WorkspacePolicy): Workspace;
50
+ }
51
+ } //# sourceMappingURL=workspace.d.ts.map
52
+ //#endregion
53
+ export { workspace };
54
+ //# sourceMappingURL=workspace.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"workspace.d.mts","names":[],"sources":["../../../../../../@warlock.js/ai-workspace/src/workspace.ts"],"mappings":";;;;;;;AA6RA;;;;;;;;AAA6D;AAE5D;;;;;;;;;;;AAWgD;;;;;;;;;;;iBAbjC,SAAA,CAAU,MAAA,EAAQ,eAAA,GAAkB,SAAS;;;;;;;;YAWjD,EAAA;;IAER,SAAA,CAAU,MAAA,EAAQ,eAAA,GAAkB,SAAS;EAAA;AAAA"}
@@ -0,0 +1,210 @@
1
+ import { WorkspacePolicyError } from "./errors.mjs";
2
+ import { createOps } from "./ops.mjs";
3
+ import { createLocalBackend } from "./backends/local.mjs";
4
+ import { createMockBackend } from "./backends/mock.mjs";
5
+ import { makeEditFileTool } from "./tools/edit-file.mjs";
6
+ import { makeGlobTool } from "./tools/glob.mjs";
7
+ import { makeGrepTool } from "./tools/grep.mjs";
8
+ import { makeReadFileTool } from "./tools/read-file.mjs";
9
+ import { makeRunShellTool } from "./tools/run-shell.mjs";
10
+ import { makeRunTestsTool } from "./tools/run-tests.mjs";
11
+ import { makeWriteFileTool } from "./tools/write-file.mjs";
12
+ import { ai } from "@warlock.js/ai";
13
+ import path from "node:path";
14
+
15
+ //#region ../@warlock.js/ai-workspace/src/workspace.ts
16
+ /**
17
+ * The full set of tool names a writable workspace vends, in a stable
18
+ * canonical order so `tools.all()` is deterministic.
19
+ */
20
+ const ALL_TOOL_NAMES = [
21
+ "readFile",
22
+ "editFile",
23
+ "writeFile",
24
+ "runShell",
25
+ "runTests",
26
+ "grep",
27
+ "glob"
28
+ ];
29
+ /**
30
+ * The subset a {@link Workspace.readonly} projection exposes — the
31
+ * non-mutating tools only. `editFile` / `writeFile` / `runShell` /
32
+ * `runTests` are deliberately omitted so a reviewer agent has no path to
33
+ * change the tree.
34
+ */
35
+ const READONLY_TOOL_NAMES = [
36
+ "readFile",
37
+ "grep",
38
+ "glob"
39
+ ];
40
+ /**
41
+ * Choose the dumb IO executor for a policy. `"mock"` selects the
42
+ * in-memory backend (hermetic tests); anything else — including the
43
+ * `"local"` default and an absent `backend` — selects the real-disk
44
+ * local backend.
45
+ */
46
+ function selectBackend(policy) {
47
+ if (policy.backend === "mock") return createMockBackend();
48
+ return createLocalBackend();
49
+ }
50
+ /**
51
+ * The internal {@link Workspace} implementation. Holds the resolved
52
+ * backend, the policy, and the single shared {@link WorkspaceOps} seam
53
+ * that both the agent-facing `.tools.*` factories and the human-facing
54
+ * direct methods funnel through — one jail, one rule set, two callers.
55
+ *
56
+ * The `allowedTools` set narrows what `tools.*` will vend and which
57
+ * mutating direct methods are permitted: a full workspace allows every
58
+ * name; a {@link WorkspaceImpl.readonly} projection allows only the
59
+ * read/grep/glob subset and rejects writes/edits/shell/mkdir/remove.
60
+ *
61
+ * Constructed via {@link workspace}; the class itself is internal.
62
+ */
63
+ var WorkspaceImpl = class WorkspaceImpl {
64
+ constructor(policy, allowedTools = ALL_TOOL_NAMES) {
65
+ this.policy = policy;
66
+ this.allowedTools = new Set(allowedTools);
67
+ const backend = selectBackend(policy);
68
+ this.ops = createOps(backend, policy);
69
+ this.tools = this.buildTools();
70
+ }
71
+ /**
72
+ * Assemble the agent-facing tool namespace. Each factory builds its
73
+ * tool over the shared `ops`; `all()` returns every *allowed* tool in
74
+ * canonical order and `pick(...)` returns the named subset (silently
75
+ * dropping any name this projection does not allow, so a `readonly()`
76
+ * workspace can never be coaxed into vending a mutating tool).
77
+ */
78
+ buildTools() {
79
+ const erase = (contract) => contract;
80
+ const factories = {
81
+ readFile: (opts) => erase(makeReadFileTool(this.ops, opts)),
82
+ editFile: (opts) => erase(makeEditFileTool(this.ops, opts)),
83
+ writeFile: (opts) => erase(makeWriteFileTool(this.ops, opts)),
84
+ runShell: (opts) => erase(makeRunShellTool(this.ops, opts)),
85
+ runTests: (opts) => erase(makeRunTestsTool(this.ops, opts)),
86
+ grep: (opts) => erase(makeGrepTool(this.ops, opts)),
87
+ glob: (opts) => erase(makeGlobTool(this.ops, opts))
88
+ };
89
+ const build = (name, opts) => factories[name](opts);
90
+ return {
91
+ all: () => ALL_TOOL_NAMES.filter((name) => this.allowedTools.has(name)).map((name) => build(name)),
92
+ pick: (...names) => names.filter((name) => this.allowedTools.has(name)).map((name) => build(name)),
93
+ readFile: (opts) => build("readFile", opts),
94
+ editFile: (opts) => build("editFile", opts),
95
+ writeFile: (opts) => build("writeFile", opts),
96
+ runShell: (opts) => build("runShell", opts),
97
+ runTests: (opts) => build("runTests", opts),
98
+ grep: (opts) => build("grep", opts),
99
+ glob: (opts) => build("glob", opts)
100
+ };
101
+ }
102
+ /**
103
+ * Reject a mutating direct method on a read-only projection — surfaced
104
+ * as a {@link WorkspacePolicyError} (the same typed error a denied
105
+ * command produces) so a caller branches on `error.type`.
106
+ */
107
+ assertWritable(operation) {
108
+ if (this.allowedTools.has("writeFile")) return;
109
+ throw new WorkspacePolicyError(`Operation "${operation}" is not permitted on a read-only workspace.`, {
110
+ type: "denied-command",
111
+ command: operation
112
+ });
113
+ }
114
+ readFile(filePath, opts) {
115
+ return this.ops.readFile(filePath, opts);
116
+ }
117
+ async writeFile(filePath, content) {
118
+ this.assertWritable("writeFile");
119
+ return this.ops.writeFile(filePath, content);
120
+ }
121
+ async editFile(input) {
122
+ this.assertWritable("editFile");
123
+ return this.ops.editFile(input);
124
+ }
125
+ async exec(command, opts) {
126
+ this.assertWritable("exec");
127
+ return this.ops.exec(command, opts);
128
+ }
129
+ grep(pattern, opts) {
130
+ return this.ops.grep(pattern, opts);
131
+ }
132
+ glob(pattern) {
133
+ return this.ops.glob(pattern);
134
+ }
135
+ exists(filePath) {
136
+ return this.ops.exists(filePath);
137
+ }
138
+ async mkdir(filePath) {
139
+ this.assertWritable("mkdir");
140
+ return this.ops.mkdir(filePath);
141
+ }
142
+ async remove(filePath) {
143
+ this.assertWritable("remove");
144
+ return this.ops.remove(filePath);
145
+ }
146
+ /**
147
+ * A read-only projection over the SAME policy — only the read/grep/glob
148
+ * tools are vended and every mutating direct method rejects with a
149
+ * {@link WorkspacePolicyError}. A fresh ops/backend is built from the
150
+ * identical policy, so the projection sees the same jailed tree.
151
+ */
152
+ readonly() {
153
+ return new WorkspaceImpl(this.policy, READONLY_TOOL_NAMES);
154
+ }
155
+ /**
156
+ * A sub-jailed view rooted at `subdir` (relative to this workspace's
157
+ * `cwd`). Returns a brand-new workspace whose policy is this policy
158
+ * with `cwd` narrowed to `join(cwd, subdir)` — same backend selection,
159
+ * same allow/deny/shell/read sub-policies, but a tighter jail root.
160
+ */
161
+ scope(subdir) {
162
+ return new WorkspaceImpl({
163
+ ...this.policy,
164
+ cwd: path.join(this.policy.cwd, subdir)
165
+ }, [...this.allowedTools]);
166
+ }
167
+ };
168
+ /**
169
+ * Build a {@link Workspace} — the integrator that wires a
170
+ * {@link WorkspacePolicy} to a backend, the shared policy-enforced ops
171
+ * layer, and the seven agent-facing tool factories.
172
+ *
173
+ * The backend is chosen from `policy.backend`: `"mock"` runs in memory
174
+ * (hermetic tests); the `"local"` default (and any absent value) runs
175
+ * over the real disk via `@warlock.js/fs` + `node:child_process`. The
176
+ * returned workspace exposes:
177
+ *
178
+ * - **`tools.*`** — `readFile` / `editFile` / `writeFile` / `runShell` /
179
+ * `runTests` / `grep` / `glob`, plus `all()` (every tool) and
180
+ * `pick(...)` (a least-privilege subset).
181
+ * - **direct methods** — `readFile` / `writeFile` / `editFile` / `exec` /
182
+ * `grep` / `glob` / `exists` / `mkdir` / `remove`, each delegating 1:1
183
+ * to the shared ops layer.
184
+ * - **`readonly()`** — a projection that vends only read/grep/glob and
185
+ * rejects every mutating direct method.
186
+ * - **`scope(subdir)`** — a sub-jailed workspace rooted at `subdir`.
187
+ *
188
+ * Available at runtime as `ai.workspace(policy)` once this module is
189
+ * imported (it registers the verb on the shared `ai` object).
190
+ *
191
+ * @param policy - The policy bounding the workspace (its `cwd` is the jail root).
192
+ * @returns A fully-wired {@link Workspace}.
193
+ *
194
+ * @example
195
+ * const ws = workspace({ cwd: "/srv/acme-api", shell: { allow: ["npm"], inheritEnv: ["PATH"] } });
196
+ * const dev = ai.agent({ model, tools: ws.tools.all() });
197
+ * await dev.execute("Make the failing cart-total suite green.");
198
+ *
199
+ * @example
200
+ * // Least-privilege reviewer — no write, no shell.
201
+ * const reviewer = ai.agent({ model, tools: ws.readonly().tools.all() });
202
+ */
203
+ function workspace(policy) {
204
+ return new WorkspaceImpl(policy);
205
+ }
206
+ ai.workspace = workspace;
207
+
208
+ //#endregion
209
+ export { workspace };
210
+ //# sourceMappingURL=workspace.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"workspace.mjs","names":[],"sources":["../../../../../../@warlock.js/ai-workspace/src/workspace.ts"],"sourcesContent":["import path from \"node:path\";\nimport { ai, type ToolContract } from \"@warlock.js/ai\";\nimport { createLocalBackend } from \"./backends/local\";\nimport { createMockBackend } from \"./backends/mock\";\nimport { WorkspacePolicyError } from \"./errors\";\nimport { createOps } from \"./ops\";\nimport { makeEditFileTool } from \"./tools/edit-file\";\nimport { makeGlobTool } from \"./tools/glob\";\nimport { makeGrepTool } from \"./tools/grep\";\nimport { makeReadFileTool } from \"./tools/read-file\";\nimport { makeRunShellTool } from \"./tools/run-shell\";\nimport { makeRunTestsTool } from \"./tools/run-tests\";\nimport { makeWriteFileTool } from \"./tools/write-file\";\nimport type {\n EditFileInput,\n EditFileResult,\n GrepResult,\n RunShellResult,\n Workspace,\n WorkspaceBackend,\n WorkspaceOps,\n WorkspacePolicy,\n WorkspaceToolName,\n WorkspaceTools,\n} from \"./contracts\";\n\n/**\n * The full set of tool names a writable workspace vends, in a stable\n * canonical order so `tools.all()` is deterministic.\n */\nconst ALL_TOOL_NAMES: readonly WorkspaceToolName[] = [\n \"readFile\",\n \"editFile\",\n \"writeFile\",\n \"runShell\",\n \"runTests\",\n \"grep\",\n \"glob\",\n];\n\n/**\n * The subset a {@link Workspace.readonly} projection exposes — the\n * non-mutating tools only. `editFile` / `writeFile` / `runShell` /\n * `runTests` are deliberately omitted so a reviewer agent has no path to\n * change the tree.\n */\nconst READONLY_TOOL_NAMES: readonly WorkspaceToolName[] = [\n \"readFile\",\n \"grep\",\n \"glob\",\n];\n\n/**\n * Choose the dumb IO executor for a policy. `\"mock\"` selects the\n * in-memory backend (hermetic tests); anything else — including the\n * `\"local\"` default and an absent `backend` — selects the real-disk\n * local backend.\n */\nfunction selectBackend(policy: WorkspacePolicy): WorkspaceBackend {\n if (policy.backend === \"mock\") {\n return createMockBackend();\n }\n\n return createLocalBackend();\n}\n\n/**\n * The internal {@link Workspace} implementation. Holds the resolved\n * backend, the policy, and the single shared {@link WorkspaceOps} seam\n * that both the agent-facing `.tools.*` factories and the human-facing\n * direct methods funnel through — one jail, one rule set, two callers.\n *\n * The `allowedTools` set narrows what `tools.*` will vend and which\n * mutating direct methods are permitted: a full workspace allows every\n * name; a {@link WorkspaceImpl.readonly} projection allows only the\n * read/grep/glob subset and rejects writes/edits/shell/mkdir/remove.\n *\n * Constructed via {@link workspace}; the class itself is internal.\n */\nclass WorkspaceImpl implements Workspace {\n /** The shared, policy-enforced operation layer (jail + guards). */\n private readonly ops: WorkspaceOps;\n\n /** Tool names this projection is permitted to vend / mutate through. */\n private readonly allowedTools: ReadonlySet<WorkspaceToolName>;\n\n public readonly policy: WorkspacePolicy;\n\n public readonly tools: WorkspaceTools;\n\n public constructor(\n policy: WorkspacePolicy,\n allowedTools: readonly WorkspaceToolName[] = ALL_TOOL_NAMES,\n ) {\n this.policy = policy;\n this.allowedTools = new Set(allowedTools);\n\n const backend = selectBackend(policy);\n this.ops = createOps(backend, policy);\n this.tools = this.buildTools();\n }\n\n /**\n * Assemble the agent-facing tool namespace. Each factory builds its\n * tool over the shared `ops`; `all()` returns every *allowed* tool in\n * canonical order and `pick(...)` returns the named subset (silently\n * dropping any name this projection does not allow, so a `readonly()`\n * workspace can never be coaxed into vending a mutating tool).\n */\n private buildTools(): WorkspaceTools {\n // Each `make*Tool` returns a precisely-typed\n // `ToolContract<SpecificInput, SpecificOutput>`, but the agent-facing\n // `WorkspaceTools` surface vends the type-erased `ToolContract`\n // (`ToolContract<unknown, unknown>`). Because `ToolContract` puts its\n // input in a contravariant position (`execute(input)` / `action(input)`),\n // a specific contract is not assignable to the erased one — so erase it\n // once, here, through `unknown`. The runtime object is identical; only\n // the static input type is widened for the shared surface.\n const erase = <TInput, TOutput>(\n contract: ToolContract<TInput, TOutput>,\n ): ToolContract => contract as unknown as ToolContract;\n\n const factories: Record<\n WorkspaceToolName,\n (opts?: { name?: string; command?: string }) => ToolContract\n > = {\n readFile: (opts) => erase(makeReadFileTool(this.ops, opts)),\n editFile: (opts) => erase(makeEditFileTool(this.ops, opts)),\n writeFile: (opts) => erase(makeWriteFileTool(this.ops, opts)),\n runShell: (opts) => erase(makeRunShellTool(this.ops, opts)),\n runTests: (opts) => erase(makeRunTestsTool(this.ops, opts)),\n grep: (opts) => erase(makeGrepTool(this.ops, opts)),\n glob: (opts) => erase(makeGlobTool(this.ops, opts)),\n };\n\n const build = (name: WorkspaceToolName, opts?: { name?: string; command?: string }) =>\n factories[name](opts);\n\n return {\n all: () =>\n ALL_TOOL_NAMES.filter((name) => this.allowedTools.has(name)).map((name) =>\n build(name),\n ),\n pick: (...names: WorkspaceToolName[]) =>\n names.filter((name) => this.allowedTools.has(name)).map((name) => build(name)),\n readFile: (opts) => build(\"readFile\", opts),\n editFile: (opts) => build(\"editFile\", opts),\n writeFile: (opts) => build(\"writeFile\", opts),\n runShell: (opts) => build(\"runShell\", opts),\n runTests: (opts) => build(\"runTests\", opts),\n grep: (opts) => build(\"grep\", opts),\n glob: (opts) => build(\"glob\", opts),\n };\n }\n\n /**\n * Reject a mutating direct method on a read-only projection — surfaced\n * as a {@link WorkspacePolicyError} (the same typed error a denied\n * command produces) so a caller branches on `error.type`.\n */\n private assertWritable(operation: string): void {\n if (this.allowedTools.has(\"writeFile\")) {\n return;\n }\n\n throw new WorkspacePolicyError(\n `Operation \"${operation}\" is not permitted on a read-only workspace.`,\n { type: \"denied-command\", command: operation },\n );\n }\n\n public readFile(\n filePath: string,\n opts?: { offset?: number; limit?: number },\n ): Promise<{ content: string; hash: string; totalLines: number }> {\n return this.ops.readFile(filePath, opts);\n }\n\n public async writeFile(\n filePath: string,\n content: string,\n ): Promise<{ hash: string; bytesWritten: number }> {\n this.assertWritable(\"writeFile\");\n\n return this.ops.writeFile(filePath, content);\n }\n\n public async editFile(input: EditFileInput): Promise<EditFileResult> {\n this.assertWritable(\"editFile\");\n\n return this.ops.editFile(input);\n }\n\n public async exec(command: string, opts?: { timeoutMs?: number }): Promise<RunShellResult> {\n this.assertWritable(\"exec\");\n\n return this.ops.exec(command, opts);\n }\n\n public grep(\n pattern: string,\n opts?: { glob?: string; ignoreCase?: boolean },\n ): Promise<GrepResult> {\n return this.ops.grep(pattern, opts);\n }\n\n public glob(pattern: string): Promise<string[]> {\n return this.ops.glob(pattern);\n }\n\n public exists(filePath: string): Promise<boolean> {\n return this.ops.exists(filePath);\n }\n\n public async mkdir(filePath: string): Promise<void> {\n this.assertWritable(\"mkdir\");\n\n return this.ops.mkdir(filePath);\n }\n\n public async remove(filePath: string): Promise<void> {\n this.assertWritable(\"remove\");\n\n return this.ops.remove(filePath);\n }\n\n /**\n * A read-only projection over the SAME policy — only the read/grep/glob\n * tools are vended and every mutating direct method rejects with a\n * {@link WorkspacePolicyError}. A fresh ops/backend is built from the\n * identical policy, so the projection sees the same jailed tree.\n */\n public readonly(): Workspace {\n return new WorkspaceImpl(this.policy, READONLY_TOOL_NAMES);\n }\n\n /**\n * A sub-jailed view rooted at `subdir` (relative to this workspace's\n * `cwd`). Returns a brand-new workspace whose policy is this policy\n * with `cwd` narrowed to `join(cwd, subdir)` — same backend selection,\n * same allow/deny/shell/read sub-policies, but a tighter jail root.\n */\n public scope(subdir: string): Workspace {\n return new WorkspaceImpl(\n { ...this.policy, cwd: path.join(this.policy.cwd, subdir) },\n [...this.allowedTools],\n );\n }\n}\n\n/**\n * Build a {@link Workspace} — the integrator that wires a\n * {@link WorkspacePolicy} to a backend, the shared policy-enforced ops\n * layer, and the seven agent-facing tool factories.\n *\n * The backend is chosen from `policy.backend`: `\"mock\"` runs in memory\n * (hermetic tests); the `\"local\"` default (and any absent value) runs\n * over the real disk via `@warlock.js/fs` + `node:child_process`. The\n * returned workspace exposes:\n *\n * - **`tools.*`** — `readFile` / `editFile` / `writeFile` / `runShell` /\n * `runTests` / `grep` / `glob`, plus `all()` (every tool) and\n * `pick(...)` (a least-privilege subset).\n * - **direct methods** — `readFile` / `writeFile` / `editFile` / `exec` /\n * `grep` / `glob` / `exists` / `mkdir` / `remove`, each delegating 1:1\n * to the shared ops layer.\n * - **`readonly()`** — a projection that vends only read/grep/glob and\n * rejects every mutating direct method.\n * - **`scope(subdir)`** — a sub-jailed workspace rooted at `subdir`.\n *\n * Available at runtime as `ai.workspace(policy)` once this module is\n * imported (it registers the verb on the shared `ai` object).\n *\n * @param policy - The policy bounding the workspace (its `cwd` is the jail root).\n * @returns A fully-wired {@link Workspace}.\n *\n * @example\n * const ws = workspace({ cwd: \"/srv/acme-api\", shell: { allow: [\"npm\"], inheritEnv: [\"PATH\"] } });\n * const dev = ai.agent({ model, tools: ws.tools.all() });\n * await dev.execute(\"Make the failing cart-total suite green.\");\n *\n * @example\n * // Least-privilege reviewer — no write, no shell.\n * const reviewer = ai.agent({ model, tools: ws.readonly().tools.all() });\n */\nexport function workspace(policy: WorkspacePolicy): Workspace {\n return new WorkspaceImpl(policy);\n}\n\n/**\n * Attach the `workspace` verb to the `ai` namespace via module augmentation,\n * per the `ai.`-namespace convention. `@warlock.js/ai` now exposes a named `Ai`\n * interface for exactly this, so after a bare `import \"@warlock.js/ai-workspace\"`,\n * `ai.workspace(...)` is globally typed — no view/cast needed.\n */\ndeclare module \"@warlock.js/ai\" {\n interface Ai {\n /** Build a policy-jailed filesystem + shell {@link Workspace}. */\n workspace(policy: WorkspacePolicy): Workspace;\n }\n}\n\n// Runtime registration: attach `workspace` onto the shared `ai` object the\n// moment this package is imported (the augmentation above types it).\nai.workspace = workspace;\n"],"mappings":"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"}