@capekai/core 1.0.5 → 1.0.6

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@capekai/core",
3
- "version": "1.0.5",
3
+ "version": "1.0.6",
4
4
  "description": "Bun-native composable agent runtime and framework for Capek.",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -78,7 +78,7 @@
78
78
  "@ai-sdk/deepseek": "^2.0.35",
79
79
  "@ai-sdk/openai": "^3.0.84",
80
80
  "@capekai/tool": "^1.0.3",
81
- "@capekai/types": "^1.0.2",
81
+ "@capekai/types": "^1.0.3",
82
82
  "@openrouter/ai-sdk-provider": "^2.3.3",
83
83
  "@zip.js/zip.js": "^2.7.60",
84
84
  "ai": "^6.0.116",
package/src/core/agent.ts CHANGED
@@ -121,6 +121,7 @@ export async function* streamChat(options: ChatOptions): AsyncGenerator<MessageE
121
121
  broadcastFn: options.broadcastFn,
122
122
  additionalPaths: effectiveAdditionalPaths,
123
123
  agentId: preconfig.id,
124
+ workspaceRootId: session?.workspaceRootId ?? undefined,
124
125
  });
125
126
 
126
127
  const selfDelegationAvailable = Boolean(aiTools.task)
@@ -33,6 +33,7 @@ export interface BuildToolsOptions {
33
33
  broadcastFn?: AskBroadcastFn;
34
34
  additionalPaths?: string[];
35
35
  agentId?: string | null;
36
+ workspaceRootId?: string;
36
37
  }
37
38
 
38
39
  export async function buildAiSdkTools(
@@ -53,6 +54,7 @@ export async function buildAiSdkTools(
53
54
  broadcastFn,
54
55
  additionalPaths,
55
56
  agentId,
57
+ workspaceRootId,
56
58
  } = options;
57
59
 
58
60
  // Resolve root session ID by walking up the parent chain
@@ -92,6 +94,7 @@ export async function buildAiSdkTools(
92
94
  modelId,
93
95
  providerId,
94
96
  additionalPaths,
97
+ workspaceRootId,
95
98
  });
96
99
  const tools: ToolMap = { ...externalTools };
97
100
 
@@ -6,6 +6,7 @@ import {
6
6
  isDefaultSessionTitle,
7
7
  isSandboxActive,
8
8
  emitTerminal,
9
+ resolveSessionWorkspace,
9
10
  } from '../runtime/host-dependencies';
10
11
  import { getDefaultPreconfig, getPreconfigOrAgent } from '../context';
11
12
  import {
@@ -552,8 +553,15 @@ export async function handleChat<Origin>(
552
553
  }
553
554
 
554
555
  const workspace = session.workspaceId ? await getWorkspace(session.workspaceId) : null;
555
- const workspacePath = workspace?.path;
556
- const additionalPaths = workspace?.additionalPaths;
556
+ const workspaceContext = await resolveSessionWorkspace({
557
+ sessionId,
558
+ workspaceId: session.workspaceId || undefined,
559
+ workspaceRootId: session.workspaceRootId ?? undefined,
560
+ workspacePath: workspace?.path,
561
+ additionalPaths: workspace?.additionalPaths,
562
+ });
563
+ const workspacePath = workspaceContext.workspacePath;
564
+ const additionalPaths = workspaceContext.additionalPaths;
557
565
 
558
566
  const preconfig = session.preconfigId
559
567
  ? await getPreconfigOrAgent(session.preconfigId)
@@ -810,8 +818,15 @@ export async function handleSessionEditMessage<Origin>(
810
818
  });
811
819
 
812
820
  const workspace = session.workspaceId ? await getWorkspace(session.workspaceId) : null;
813
- const workspacePath = workspace?.path;
814
- const additionalPaths = workspace?.additionalPaths;
821
+ const workspaceContext = await resolveSessionWorkspace({
822
+ sessionId: msg.sessionId,
823
+ workspaceId: session.workspaceId || undefined,
824
+ workspaceRootId: session.workspaceRootId ?? undefined,
825
+ workspacePath: workspace?.path,
826
+ additionalPaths: workspace?.additionalPaths,
827
+ });
828
+ const workspacePath = workspaceContext.workspacePath;
829
+ const additionalPaths = workspaceContext.additionalPaths;
815
830
 
816
831
  const preconfig = session.preconfigId
817
832
  ? await getPreconfigOrAgent(session.preconfigId)
@@ -19,7 +19,7 @@ import {
19
19
  } from '../../runtime/domain-tool-source';
20
20
  import { isToolAllowedInContext, type ToolExecutionScope } from '../tool-capabilities';
21
21
  import type { ToolMap } from './types';
22
- import type { BroadcastFn } from '../../runtime/host-dependencies';
22
+ import { resolveToolDefinition, type BroadcastFn } from '../../runtime/host-dependencies';
23
23
  import {
24
24
  capekToolOutputToAiSdk,
25
25
  createCapekToolOutputEnvelope,
@@ -39,6 +39,7 @@ export interface ExternalToolsOptions {
39
39
  modelId?: string;
40
40
  providerId?: string;
41
41
  additionalPaths?: string[];
42
+ workspaceRootId?: string;
42
43
  }
43
44
 
44
45
  export async function buildExternalTools(options: ExternalToolsOptions): Promise<ToolMap> {
@@ -56,6 +57,7 @@ export async function buildExternalTools(options: ExternalToolsOptions): Promise
56
57
  modelId,
57
58
  providerId,
58
59
  additionalPaths,
60
+ workspaceRootId,
59
61
  } = options;
60
62
 
61
63
  const tools: ToolMap = {};
@@ -83,7 +85,14 @@ export async function buildExternalTools(options: ExternalToolsOptions): Promise
83
85
  const loadedTool = await getTool(name);
84
86
  if (!loadedTool) continue;
85
87
 
86
- const { definition } = loadedTool;
88
+ const definition = await resolveToolDefinition({
89
+ sessionId,
90
+ workspaceId,
91
+ workspaceRootId,
92
+ workspacePath,
93
+ definition: loadedTool.definition,
94
+ });
95
+ if (!definition) continue;
87
96
 
88
97
  if (!isToolAllowedInContext(definition.capabilities, executionScopes)) {
89
98
  continue;
@@ -22,7 +22,9 @@ export {
22
22
  type PermissionRequestStatus,
23
23
  type RuntimeHost,
24
24
  type SandboxBindings,
25
+ type SessionWorkspaceContext,
25
26
  type TitleHost,
27
+ type ToolPolicyHost,
26
28
  type WorkspaceCapabilityBindings,
27
29
  } from '../runtime/host';
28
30
  export { createStandaloneHost } from '../runtime/standalone-host';
@@ -1,5 +1,6 @@
1
1
  import type { Session } from '@capekai/types';
2
- import { getRuntimeHost } from './host';
2
+ import type { ToolDefinition } from '@capekai/tool';
3
+ import { getOptionalRuntimeHost, getRuntimeHost } from './host';
3
4
  import type { RuntimeAudience, RuntimeDelivery, RuntimeEvent } from './events';
4
5
 
5
6
  type HostRuntimeAudience = Exclude<RuntimeAudience, { scope: 'origin' }>;
@@ -66,6 +67,38 @@ export const generateSessionTitle = (...args: Parameters<ReturnType<typeof getRu
66
67
  getRuntimeHost().titles.generateSessionTitle(...args);
67
68
  export const getToolWorkspaceHost = (...args: Parameters<ReturnType<typeof getRuntimeHost>['workspace']['createToolWorkspaceHost']>) =>
68
69
  getRuntimeHost().workspace.createToolWorkspaceHost(...args);
70
+
71
+ export async function resolveSessionWorkspace(options: {
72
+ sessionId: string;
73
+ workspaceId?: string;
74
+ workspaceRootId?: string;
75
+ workspacePath?: string;
76
+ additionalPaths?: string[];
77
+ }): Promise<{ workspacePath?: string; additionalPaths?: string[] }> {
78
+ const resolver = getRuntimeHost().workspace.resolveSessionWorkspace;
79
+ if (resolver) {
80
+ return await resolver(options);
81
+ }
82
+ if (options.workspaceRootId) {
83
+ throw new Error('Session workspace root requires a host resolver');
84
+ }
85
+ return {
86
+ workspacePath: options.workspacePath,
87
+ additionalPaths: options.additionalPaths,
88
+ };
89
+ }
90
+
91
+ export async function resolveToolDefinition(options: {
92
+ sessionId: string;
93
+ workspaceId?: string;
94
+ workspaceRootId?: string;
95
+ workspacePath?: string;
96
+ definition: ToolDefinition;
97
+ }): Promise<ToolDefinition | null> {
98
+ const resolver = getOptionalRuntimeHost()?.toolPolicy?.resolveDefinition;
99
+ return resolver ? await resolver(options) : options.definition;
100
+ }
101
+
69
102
  export const isSandboxActive = (): boolean => getRuntimeHost().sandbox.isSandboxActive();
70
103
 
71
104
  export type { RuntimeEventSink, RuntimeEventSink as BroadcastFn } from './events';
@@ -1,6 +1,6 @@
1
1
  import { AsyncLocalStorage } from 'node:async_hooks';
2
2
  import type { HostLayout } from './host-layout';
3
- import type { Ask } from '@capekai/tool';
3
+ import type { Ask, ToolDefinition } from '@capekai/tool';
4
4
  import type {
5
5
  AskRequestMessage, AskTimedOutMessage, AutoApproveSeverity, MessageWithParts, Session,
6
6
  } from '@capekai/types';
@@ -82,7 +82,19 @@ export interface TitleHost {
82
82
  generateSessionTitle(messages: MessageWithParts[]): Promise<string | null>;
83
83
  }
84
84
 
85
+ export interface SessionWorkspaceContext {
86
+ workspacePath?: string;
87
+ additionalPaths?: string[];
88
+ }
89
+
85
90
  export interface WorkspaceCapabilityBindings {
91
+ resolveSessionWorkspace?(options: {
92
+ sessionId: string;
93
+ workspaceId?: string;
94
+ workspaceRootId?: string;
95
+ workspacePath?: string;
96
+ additionalPaths?: string[];
97
+ }): SessionWorkspaceContext | Promise<SessionWorkspaceContext>;
86
98
  createToolWorkspaceHost(options: {
87
99
  workspaceId?: string;
88
100
  workspacePath?: string;
@@ -91,6 +103,16 @@ export interface WorkspaceCapabilityBindings {
91
103
  }): WorkspaceCapabilityHost;
92
104
  }
93
105
 
106
+ export interface ToolPolicyHost {
107
+ resolveDefinition?(options: {
108
+ sessionId: string;
109
+ workspaceId?: string;
110
+ workspaceRootId?: string;
111
+ workspacePath?: string;
112
+ definition: ToolDefinition;
113
+ }): ToolDefinition | null | Promise<ToolDefinition | null>;
114
+ }
115
+
94
116
  export interface SandboxBindings {
95
117
  isSandboxActive(): boolean;
96
118
  }
@@ -100,6 +122,7 @@ export interface RuntimeHost {
100
122
  delivery: DeliveryHost;
101
123
  titles: TitleHost;
102
124
  workspace: WorkspaceCapabilityBindings;
125
+ toolPolicy?: ToolPolicyHost;
103
126
  sandbox: SandboxBindings;
104
127
  /** Host-supplied filesystem layout policy. */
105
128
  layout?: HostLayout;
@@ -122,8 +145,12 @@ export function withRuntimeHost<T>(value: RuntimeHost, callback: () => T): T {
122
145
  return scopedHost.run(value, callback);
123
146
  }
124
147
 
148
+ export function getOptionalRuntimeHost(): RuntimeHost | null {
149
+ return scopedHost.getStore() ?? host;
150
+ }
151
+
125
152
  export function getRuntimeHost(): RuntimeHost {
126
- const active = scopedHost.getStore() ?? host;
153
+ const active = getOptionalRuntimeHost();
127
154
  if (!active) throw new Error('Runtime host has not been configured');
128
155
  return active;
129
156
  }
@@ -5,6 +5,7 @@ import {
5
5
  emitTerminal,
6
6
  emitToAskTargets,
7
7
  emitToController,
8
+ resolveSessionWorkspace,
8
9
  } from '../runtime/host-dependencies';
9
10
  import { getLLMSubagentMaxSteps } from '../configuration/runtime';
10
11
  import {
@@ -59,9 +60,21 @@ export async function executeChildSession(options: {
59
60
  streamChat = streamChatWithRetry,
60
61
  } = options;
61
62
 
62
- // Resolve additionalPaths from workspace
63
- const workspace = workspaceId ? await getWorkspace(workspaceId) : null;
64
- const additionalPaths = workspace?.additionalPaths;
63
+ // Re-resolve the child session's host-owned root. Async child execution may
64
+ // enter after the parent context was assembled, so the persisted child
65
+ // binding is authoritative.
66
+ const childSession = await getSession(childSessionId);
67
+ const effectiveWorkspaceId = workspaceId ?? childSession?.workspaceId ?? undefined;
68
+ const workspace = effectiveWorkspaceId ? await getWorkspace(effectiveWorkspaceId) : null;
69
+ const workspaceContext = await resolveSessionWorkspace({
70
+ sessionId: childSessionId,
71
+ workspaceId: effectiveWorkspaceId,
72
+ workspaceRootId: childSession?.workspaceRootId ?? undefined,
73
+ workspacePath: workspacePath ?? workspace?.path,
74
+ additionalPaths: workspace?.additionalPaths,
75
+ });
76
+ const effectiveWorkspacePath = workspaceContext.workspacePath;
77
+ const additionalPaths = workspaceContext.additionalPaths;
65
78
 
66
79
  let messages: MessageWithParts[];
67
80
 
@@ -172,8 +185,8 @@ export async function executeChildSession(options: {
172
185
  sessionId: childSessionId,
173
186
  preconfig,
174
187
  messages,
175
- workspacePath,
176
- workspaceId,
188
+ workspacePath: effectiveWorkspacePath,
189
+ workspaceId: effectiveWorkspaceId,
177
190
  additionalPaths,
178
191
  modelId: modelId ?? undefined,
179
192
  providerId: providerId ?? undefined,
@@ -429,6 +429,7 @@ async function runSubagent(
429
429
  childSession = await createSessionFn({
430
430
  id: randomUUID(),
431
431
  workspaceId: workspaceId || parentSession?.workspaceId || '',
432
+ workspaceRootId: parentSession?.workspaceRootId ?? null,
432
433
  preconfigId: subagent_type,
433
434
  title: `${description} (@${subagent_type} subagent)`,
434
435
  status: 'active',