@capekai/core 1.0.2 → 1.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -10,3 +10,46 @@ Requires Bun 1.3 or newer.
10
10
  npm install @capekai/core
11
11
  ```
12
12
  Public subpaths include `composition`, `plugins`, `hosts`, `execution`, `providers`, `tools`, `ask-authority`, `sandbox`, `workspace`, `configuration`, `tool`, and `storage`.
13
+
14
+ ## Workspace policy
15
+
16
+ Čapek owns path resolution and containment. Embedding hosts can supply their own blocked paths, sensitive patterns, and home directory when composing an agent:
17
+
18
+ ```ts
19
+ import { createComposition } from '@capekai/core/composition';
20
+
21
+ const composition = await createComposition(processScope, {
22
+ ...values,
23
+ workspacePolicy: {
24
+ blockedPaths: ['/proc/', '/sys/'],
25
+ sensitivePatterns: ['.env', '.pem', '.key'],
26
+ homeDir: '/home/agent',
27
+ },
28
+ });
29
+ ```
30
+
31
+ Hosts building a custom plugin profile can configure the same policy directly:
32
+
33
+ ```ts
34
+ import { workspacePolicyPlugin } from '@capekai/core/plugins';
35
+
36
+ workspacePolicyPlugin('host.workspace-policy', {
37
+ blockedPaths: [],
38
+ sensitivePatterns: ['credentials'],
39
+ homeDir: '/srv/agent',
40
+ });
41
+ ```
42
+
43
+ For workspace helpers used outside an agent scope, configure the process-wide policy during host bootstrap:
44
+
45
+ ```ts
46
+ import { configureWorkspacePolicy } from '@capekai/core/workspace';
47
+
48
+ configureWorkspacePolicy({
49
+ blockedPaths: ['/proc/', '/sys/'],
50
+ sensitivePatterns: ['.env', '.pem', '.key'],
51
+ homeDir: '/home/agent',
52
+ });
53
+ ```
54
+
55
+ Call `configureWorkspacePolicy()` with no argument, or omit composition options, to retain the compatibility defaults.
package/package.json CHANGED
@@ -1,11 +1,11 @@
1
1
  {
2
2
  "name": "@capekai/core",
3
- "version": "1.0.2",
3
+ "version": "1.0.4",
4
4
  "description": "Bun-native composable agent runtime and framework for Capek.",
5
5
  "license": "MIT",
6
6
  "repository": {
7
7
  "type": "git",
8
- "url": "git+https://github.com/capek-dev/prokop.git",
8
+ "url": "git+https://github.com/capek-dev/capek.git",
9
9
  "directory": "packages/capek"
10
10
  },
11
11
  "type": "module",
@@ -77,7 +77,7 @@
77
77
  "dependencies": {
78
78
  "@ai-sdk/deepseek": "^2.0.35",
79
79
  "@ai-sdk/openai": "^3.0.84",
80
- "@capekai/tool": "^1.0.0",
80
+ "@capekai/tool": "^1.0.2",
81
81
  "@capekai/types": "^1.0.1",
82
82
  "@openrouter/ai-sdk-provider": "^2.3.3",
83
83
  "@zip.js/zip.js": "^2.7.60",
@@ -9,6 +9,7 @@
9
9
  */
10
10
 
11
11
  export {
12
+ configureWorkspacePolicy,
12
13
  expandPath,
13
14
  isInsideUnselectedAdditionalRoot,
14
15
  isPathInside,
@@ -16,6 +16,7 @@ import type { SandboxController } from '../sandbox/controller';
16
16
  import type { StorageBundle } from '../storage/contracts';
17
17
  import type { ToolRegistryResolver } from '../tools/registry';
18
18
  import type { WorkspaceToolDiscovery } from '../tools/tool-source';
19
+ import type { WorkspacePolicyOptions } from '../workspace/contracts';
19
20
  import { getSchedulerHost } from '../scheduler/host';
20
21
  import { getSessionSearchHost } from '../session-search/host';
21
22
  import { createContextSectionsPlugin } from './context-sections';
@@ -64,6 +65,9 @@ export interface FacadeScopeValues {
64
65
  host: RuntimeHost;
65
66
  contextSources: Partial<ContextSources>;
66
67
  workspaceToolDiscovery: WorkspaceToolDiscovery;
68
+ /** Host-owned path classification values. When omitted, the current
69
+ * compatibility defaults apply. */
70
+ workspacePolicy?: WorkspacePolicyOptions;
67
71
  /** Optional compatibility resolver. When omitted, the facade
68
72
  * composition derives the resolver from the composed scope's effective
69
73
  * contributed tool payloads. The explicit value is the rollback
@@ -109,7 +113,7 @@ export function createFacadeAgentPlugins(values: FacadeScopeValues): readonly Ca
109
113
  retryPolicyPlugin('facade.retry-policy'),
110
114
  compactionPolicyPlugin('facade.compaction-policy'),
111
115
  permissionPolicyPlugin('facade.permission-policy'),
112
- workspacePolicyPlugin('facade.workspace-policy'),
116
+ workspacePolicyPlugin('facade.workspace-policy', values.workspacePolicy),
113
117
  toolOutputPolicyPlugin('facade.tool-output-policy'),
114
118
  contextSourcesValuePlugin('facade.context-sources', values.contextSources),
115
119
  // Facade context parity: the facade keeps the legacy self-delegation and
@@ -1,11 +1,6 @@
1
1
  import type { CapekPlugin, PluginContext } from '../kernel/types';
2
- import { homedir } from 'os';
3
- import { SENSITIVE_FILE_PATTERNS } from '@capekai/types';
4
- import {
5
- BLOCKED_PATHS,
6
- createWorkspaceService,
7
- type WorkspacePolicyOptions,
8
- } from '../workspace/policy';
2
+ import type { WorkspacePolicyOptions } from '../workspace/contracts';
3
+ import { createWorkspaceService } from '../workspace/policy';
9
4
  import { capekWorkspacePolicyKey } from './service-keys';
10
5
 
11
6
  /**
@@ -17,17 +12,15 @@ import { capekWorkspacePolicyKey } from './service-keys';
17
12
  * current containment, root classification, expansion, and sensitive/blocked
18
13
  * denial behavior.
19
14
  */
20
- export function workspacePolicyPlugin(id: string): CapekPlugin<unknown> {
15
+ export function workspacePolicyPlugin(
16
+ id: string,
17
+ options?: WorkspacePolicyOptions,
18
+ ): CapekPlugin<unknown> {
21
19
  return {
22
20
  id,
23
21
  scope: 'agent',
24
22
  provides: [capekWorkspacePolicyKey],
25
23
  setup(context: PluginContext) {
26
- const options: WorkspacePolicyOptions = {
27
- blockedPaths: [...BLOCKED_PATHS],
28
- sensitivePatterns: [...SENSITIVE_FILE_PATTERNS],
29
- homeDir: homedir(),
30
- };
31
24
  context.provide(
32
25
  capekWorkspacePolicyKey,
33
26
  createWorkspaceService({ id, options }),
@@ -205,6 +205,7 @@ export async function executeTool(options: ExecuteToolOptions): Promise<ToolResu
205
205
  logger: createLogger(tool.definition.name, sessionId),
206
206
  fetch: globalThis.fetch.bind(globalThis),
207
207
  resolvePath: workspace.resolvePath,
208
+ resolvePathFrom: workspace.resolvePathFrom,
208
209
  isWithinWorkspace: workspace.isWithinWorkspace,
209
210
  isSensitivePath: workspace.isSensitivePath,
210
211
  isBlockedPath: workspace.isBlockedPath,
@@ -41,6 +41,7 @@ export interface WorkspaceCapability {
41
41
  allowedRoots: string[];
42
42
  tempDir: string;
43
43
  resolvePath(path: string): string;
44
+ resolvePathFrom(path: string, basePath: string): string;
44
45
  isWithinWorkspace(path: string): boolean;
45
46
  isSensitivePath(path: string): boolean;
46
47
  isBlockedPath(path: string): boolean;
@@ -47,6 +47,14 @@ function defaultOptions(): WorkspacePolicyOptions {
47
47
  };
48
48
  }
49
49
 
50
+ function freezeOptions(options: WorkspacePolicyOptions): Readonly<WorkspacePolicyOptions> {
51
+ return Object.freeze({
52
+ blockedPaths: Object.freeze([...options.blockedPaths]),
53
+ sensitivePatterns: Object.freeze([...options.sensitivePatterns]),
54
+ homeDir: options.homeDir,
55
+ });
56
+ }
57
+
50
58
  // ── Mandatory containment runtime (C6 step 6) ───────────────────────────
51
59
  // The tool-runtime capability is constructed HERE, not by provider methods:
52
60
  // a custom provider supplies only frozen options (blocked paths, sensitive
@@ -78,14 +86,18 @@ export function createWorkspaceCapabilityWithOptions(
78
86
  const additionalRoots = (host.additionalRoots ?? []).map((path) => resolve(path));
79
87
  const allowedRoots = (host.allowedRoots ?? []).map((path) => resolve(path));
80
88
 
81
- function resolvePath(path: string): string {
89
+ function resolvePathFrom(path: string, basePath: string): string {
82
90
  if (path === '~' || path.startsWith('~/')) {
83
91
  return join(options.homeDir, path.slice(1));
84
92
  }
85
93
  if (isAbsolute(path)) {
86
94
  return resolve(path);
87
95
  }
88
- return resolve(effectiveRoot, path);
96
+ return resolve(basePath, path);
97
+ }
98
+
99
+ function resolvePath(path: string): string {
100
+ return resolvePathFrom(path, effectiveRoot);
89
101
  }
90
102
 
91
103
  return {
@@ -94,6 +106,7 @@ export function createWorkspaceCapabilityWithOptions(
94
106
  allowedRoots,
95
107
  tempDir: host.tempDir,
96
108
  resolvePath,
109
+ resolvePathFrom,
97
110
  isWithinWorkspace(path: string): boolean {
98
111
  const resolvedPath = resolvePath(path);
99
112
  return [effectiveRoot, ...additionalRoots]
@@ -125,7 +138,7 @@ export function createWorkspaceService(
125
138
  createOptions: WorkspaceServiceCreateOptions = {},
126
139
  ): WorkspaceService {
127
140
  const id = createOptions.id ?? 'workspace.default';
128
- const options = createOptions.options ?? defaultOptions();
141
+ const options = freezeOptions(createOptions.options ?? defaultOptions());
129
142
 
130
143
  const service: WorkspaceService = {
131
144
  id,
@@ -246,6 +259,15 @@ export function getWorkspaceService(): WorkspaceService {
246
259
  ?? (processDefaultService ??= createWorkspaceService({ id: 'workspace.process-default' }));
247
260
  }
248
261
 
262
+ /** Configures the process-wide policy used outside an agent scope. Omitting
263
+ * options restores the compatibility defaults. */
264
+ export function configureWorkspacePolicy(options?: WorkspacePolicyOptions): void {
265
+ processDefaultService = createWorkspaceService({
266
+ id: 'workspace.process-default',
267
+ options,
268
+ });
269
+ }
270
+
249
271
  /** Builds the tool-runtime capability over the active workspace policy. */
250
272
  export function createWorkspaceCapability(host: WorkspaceCapabilityHost): WorkspaceCapability {
251
273
  return createWorkspaceCapabilityWithOptions(host, getWorkspaceService().options);