@capekai/core 1.0.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 (148) hide show
  1. package/README.md +12 -0
  2. package/package.json +105 -0
  3. package/src/adapters/ai-sdk.ts +84 -0
  4. package/src/compaction/contracts.ts +82 -0
  5. package/src/compaction/executor.ts +161 -0
  6. package/src/compaction/policy.ts +318 -0
  7. package/src/compaction/recovery.ts +139 -0
  8. package/src/compaction/task.ts +540 -0
  9. package/src/configuration/contracts.ts +58 -0
  10. package/src/configuration/defaults.ts +27 -0
  11. package/src/configuration/runtime.ts +42 -0
  12. package/src/configuration/single-model.ts +75 -0
  13. package/src/context/assembler.ts +112 -0
  14. package/src/context/index.ts +2 -0
  15. package/src/context/sources.ts +119 -0
  16. package/src/context/workspace.ts +63 -0
  17. package/src/core/agent.ts +401 -0
  18. package/src/core/build-tools.ts +139 -0
  19. package/src/core/chat-handler.ts +858 -0
  20. package/src/core/error-handling.ts +18 -0
  21. package/src/core/fork.ts +103 -0
  22. package/src/core/interrupt.ts +192 -0
  23. package/src/core/message-utils.ts +261 -0
  24. package/src/core/model-utils.ts +149 -0
  25. package/src/core/part-utils.ts +88 -0
  26. package/src/core/provider-utils.ts +67 -0
  27. package/src/core/revert.ts +46 -0
  28. package/src/core/step-handlers.ts +157 -0
  29. package/src/core/stream/finalization.ts +65 -0
  30. package/src/core/stream/stream-config.ts +82 -0
  31. package/src/core/stream-handlers.ts +242 -0
  32. package/src/core/structured-output.ts +68 -0
  33. package/src/core/tool-builders/agent-tools.ts +71 -0
  34. package/src/core/tool-builders/external-tools.ts +179 -0
  35. package/src/core/tool-builders/types.ts +16 -0
  36. package/src/core/tool-builders/workspace-tools.ts +293 -0
  37. package/src/core/tool-capabilities.ts +65 -0
  38. package/src/goals/evaluator.ts +171 -0
  39. package/src/goals/index.ts +3 -0
  40. package/src/goals/loop.ts +167 -0
  41. package/src/goals/service.ts +39 -0
  42. package/src/index.ts +10 -0
  43. package/src/internal/ask-authority.ts +29 -0
  44. package/src/internal/composition.ts +44 -0
  45. package/src/internal/configuration.ts +22 -0
  46. package/src/internal/execution.ts +108 -0
  47. package/src/internal/hosts.ts +64 -0
  48. package/src/internal/plugins.ts +71 -0
  49. package/src/internal/providers.ts +32 -0
  50. package/src/internal/sandbox.ts +19 -0
  51. package/src/internal/tools.ts +48 -0
  52. package/src/internal/workspace.ts +25 -0
  53. package/src/kernel/diagnostics.ts +249 -0
  54. package/src/kernel/errors.ts +120 -0
  55. package/src/kernel/events.ts +82 -0
  56. package/src/kernel/index.ts +72 -0
  57. package/src/kernel/kernel.ts +62 -0
  58. package/src/kernel/lifecycle.ts +72 -0
  59. package/src/kernel/plugin.ts +218 -0
  60. package/src/kernel/registry.ts +493 -0
  61. package/src/kernel/scope.ts +776 -0
  62. package/src/kernel/service-key.ts +19 -0
  63. package/src/kernel/types.ts +317 -0
  64. package/src/memory/index.ts +2 -0
  65. package/src/memory/memory-tool.ts +75 -0
  66. package/src/memory/registry.ts +172 -0
  67. package/src/permission/ask-user-api.ts +70 -0
  68. package/src/permission/contracts.ts +135 -0
  69. package/src/permission/permission-request-manager.ts +58 -0
  70. package/src/permission/policy.ts +277 -0
  71. package/src/permission/runtime.ts +612 -0
  72. package/src/plugins/compaction-policy.ts +46 -0
  73. package/src/plugins/compose.ts +171 -0
  74. package/src/plugins/context-sections.ts +246 -0
  75. package/src/plugins/default-agent-driver.ts +14 -0
  76. package/src/plugins/facade-plugins.ts +129 -0
  77. package/src/plugins/goal-domain.ts +82 -0
  78. package/src/plugins/legacy-system-message.ts +152 -0
  79. package/src/plugins/loaded-tools.ts +23 -0
  80. package/src/plugins/memory-domain.ts +264 -0
  81. package/src/plugins/orchestrator-session.ts +29 -0
  82. package/src/plugins/permission-policy.ts +49 -0
  83. package/src/plugins/retry-policy.ts +28 -0
  84. package/src/plugins/scheduler-domain.ts +192 -0
  85. package/src/plugins/service-keys.ts +294 -0
  86. package/src/plugins/session-search-domain.ts +238 -0
  87. package/src/plugins/skills-domain.ts +272 -0
  88. package/src/plugins/subagent-domain.ts +287 -0
  89. package/src/plugins/tool-catalog.ts +78 -0
  90. package/src/plugins/tool-output-policy.ts +52 -0
  91. package/src/plugins/value-plugins.ts +150 -0
  92. package/src/plugins/workflow-domain.ts +198 -0
  93. package/src/plugins/workspace-policy.ts +37 -0
  94. package/src/providers/registry.ts +63 -0
  95. package/src/providers/types.ts +44 -0
  96. package/src/retry/policy.ts +282 -0
  97. package/src/retry/stream-chat.ts +312 -0
  98. package/src/runtime/agent-runtime.ts +83 -0
  99. package/src/runtime/default-agent-driver.ts +23 -0
  100. package/src/runtime/domain-tool-source.ts +156 -0
  101. package/src/runtime/events.ts +61 -0
  102. package/src/runtime/host-dependencies.ts +71 -0
  103. package/src/runtime/host-guidance.ts +22 -0
  104. package/src/runtime/host-layout.ts +23 -0
  105. package/src/runtime/host.ts +129 -0
  106. package/src/runtime/standalone-host.ts +118 -0
  107. package/src/sandbox/controller.ts +204 -0
  108. package/src/sandbox/model.ts +305 -0
  109. package/src/sandbox/provider.ts +53 -0
  110. package/src/sandbox/types.ts +110 -0
  111. package/src/scheduler/host.ts +22 -0
  112. package/src/scheduler/scheduler-tool.ts +172 -0
  113. package/src/session-search/host.ts +56 -0
  114. package/src/session-search/index.ts +23 -0
  115. package/src/session-search/session-search-tool.ts +151 -0
  116. package/src/skills/index.ts +3 -0
  117. package/src/skills/registry.ts +63 -0
  118. package/src/skills/skill-manage-tool.ts +205 -0
  119. package/src/skills/skill-tool.ts +42 -0
  120. package/src/storage/contracts.ts +159 -0
  121. package/src/storage/memory.ts +321 -0
  122. package/src/storage/options.ts +75 -0
  123. package/src/storage/runtime.ts +115 -0
  124. package/src/storage/sqlite-tool-output-artifacts.ts +106 -0
  125. package/src/storage/sqlite.ts +321 -0
  126. package/src/storage/tool-output-artifacts.ts +75 -0
  127. package/src/storage.ts +31 -0
  128. package/src/subagent/child-session.ts +282 -0
  129. package/src/subagent/guidance.ts +8 -0
  130. package/src/subagent/policy.ts +198 -0
  131. package/src/subagent/task-tool.ts +584 -0
  132. package/src/tool-output/contracts.ts +111 -0
  133. package/src/tool-output/policy.ts +410 -0
  134. package/src/tool.ts +1 -0
  135. package/src/tools/executor.ts +258 -0
  136. package/src/tools/install-manifest.ts +40 -0
  137. package/src/tools/llm-api.ts +77 -0
  138. package/src/tools/registry.ts +206 -0
  139. package/src/tools/tool-artifact.ts +182 -0
  140. package/src/tools/tool-source.ts +53 -0
  141. package/src/utils/errors.ts +334 -0
  142. package/src/utils/strip-visualization.ts +50 -0
  143. package/src/workflow/decomposer.ts +139 -0
  144. package/src/workflow/execution.ts +523 -0
  145. package/src/workflow/orchestrator-session.ts +161 -0
  146. package/src/workflow/synthesizer.ts +130 -0
  147. package/src/workspace/contracts.ts +135 -0
  148. package/src/workspace/policy.ts +327 -0
@@ -0,0 +1,135 @@
1
+ /**
2
+ * C6 permission policy contracts.
3
+ *
4
+ * The agent-scoped permission surface is split into two contracts:
5
+ *
6
+ * - `AskPermissionPolicyService` (REPLACEABLE advice/config): frozen
7
+ * timeouts, authority and value extraction, the response validators, the
8
+ * auto-approval bound, permission-key derivation, and dangerous-shell
9
+ * detection. A custom provider may replace all of these, but its advice
10
+ * can never change a mandatory decision: the runtime owns denial and
11
+ * grant construction.
12
+ * - `PermissionRuntimeService` (NON-REPLACEABLE lifecycle): request-id
13
+ * routing, pending-ask and waiter registries, timeouts and cleanup,
14
+ * response validation against the module-level validators, raw-audit
15
+ * denial, waiter resolution, canonical grant construction and persistence
16
+ * (module-level `buildGrantParams` with the dangerous-shell once
17
+ * downgrade), and the persistence calls through the interaction host.
18
+ *
19
+ * The Jean2 adapters keep persistence, WebSocket delivery, notifications,
20
+ * and controller authority: every host-facing operation goes through the
21
+ * `InteractionHost` installed by the compatibility bindings.
22
+ */
23
+
24
+ import type { Ask, AskApi, AskPermissionResponse } from '@capekai/tool';
25
+ import type { PermissionRiskLevel } from '@capekai/tool';
26
+ import type { AskAuthority, AskRequestMessage, AskTimedOutMessage, ClientCapability } from '@capekai/types';
27
+ import type { PendingAskRecord } from '../runtime/host';
28
+
29
+ /** Provider options translated at composition. The generic ask timeout has
30
+ * no current configuration source and stays the fixed 5-minute constant;
31
+ * the permission timeout translates from the composed runtime host's
32
+ * `getPermissionTimeoutMs()`. */
33
+ export interface AskPermissionPolicyOptions {
34
+ askTimeoutMs: number;
35
+ permissionTimeoutMs: number;
36
+ }
37
+
38
+ export interface RequestPermissionParams {
39
+ sessionId: string;
40
+ rootSessionId?: string;
41
+ workspaceId?: string;
42
+ toolCallId: string;
43
+ toolName: string;
44
+ ask: Ask;
45
+ broadcastFn: PermissionBroadcastFn;
46
+ timeoutMs?: number;
47
+ }
48
+
49
+ export type AskBroadcastFn = (message: AskRequestMessage | AskTimedOutMessage) => void;
50
+ export type PermissionBroadcastFn = (message: AskRequestMessage | AskTimedOutMessage) => void;
51
+
52
+ /**
53
+ * Replaceable permission advice and configuration. Every method is pure
54
+ * advice over the injected interaction host; the runtime never trusts these
55
+ * methods for mandatory denial or grant decisions.
56
+ */
57
+ export interface AskPermissionPolicyService {
58
+ readonly id: string;
59
+ /** Frozen provider options. */
60
+ readonly options: Readonly<AskPermissionPolicyOptions>;
61
+ readonly askTimeoutMs: number;
62
+ readonly permissionTimeoutMs: number;
63
+ /** Authority for a generic ask: capability asks targeting the client
64
+ * resolve to global/first_eligible with the required capability; every
65
+ * other ask keeps the controller-only default. */
66
+ resolveAskAuthority(request: Ask): AskAuthority;
67
+ /** Extracts the resolved value from a shaped generic response. */
68
+ extractResolutionValue(response: unknown): unknown;
69
+ /** A permission response is only valid when it is shaped
70
+ * `{ type: 'permission', grant: 'once' | 'session' | 'workspace' | 'deny' }`. */
71
+ isValidPermissionResponse(response: unknown): response is AskPermissionResponse;
72
+ /** True for a valid non-deny permission response. */
73
+ isPermissionApproved(response: unknown): boolean;
74
+ /** Risk ordering check used for auto-approval bounds. */
75
+ isRiskAtOrBelow(risk: PermissionRiskLevel, max: PermissionRiskLevel): boolean;
76
+ /** Server auto-approval advice: true when the session severity is above
77
+ * off/undefined and the ask's risk is within it. */
78
+ shouldAutoApprove(sessionId: string, ask: Ask): Promise<boolean>;
79
+ /** Permission key precedence: first pattern, then resource, then tool name. */
80
+ buildPermissionKey(
81
+ toolName: string,
82
+ resource: string | undefined,
83
+ patterns: string[] | undefined,
84
+ ): string;
85
+ /** Dangerous shell identity detection advice. */
86
+ isDangerousShellIdentity(identity: string | undefined): boolean;
87
+ }
88
+
89
+ /**
90
+ * Non-replaceable permission runtime and lifecycle. Owns the pending-ask
91
+ * and waiter registries, request-id routing, timeout/cleanup, response
92
+ * validation, raw-audit denial, waiter resolution, and canonical grant
93
+ * construction/persistence.
94
+ */
95
+ export interface PermissionRuntimeService {
96
+ readonly id: string;
97
+ /** The advice provider currently active for this runtime (the scoped
98
+ * provider while a scope is entered, otherwise the bound provider). */
99
+ readonly provider: AskPermissionPolicyService;
100
+
101
+ // Generic ask lifecycle (former tools/ask-user-api.ts surface).
102
+ createAskApi(
103
+ sessionId: string,
104
+ toolCallId: string,
105
+ toolName: string,
106
+ broadcastFn: AskBroadcastFn,
107
+ workspaceId?: string,
108
+ rootSessionId?: string,
109
+ ): AskApi;
110
+ resolveAsk(toolCallId: string, response: unknown, requestId?: string): Promise<boolean>;
111
+ rejectAsk(toolCallId: string, error: Error): Promise<boolean>;
112
+ rejectPendingAsksByToolCallId(toolCallId: string, error?: Error): Promise<string[]>;
113
+ rejectPendingAsksBySession(sessionId: string, error?: Error): Promise<string[]>;
114
+ hasPendingAsk(toolCallId: string): boolean;
115
+ getAuthorityForPendingAsk(toolCallId: string): AskAuthority | undefined;
116
+ getSessionIdForPendingAsk(toolCallId: string, requestId?: string): Promise<string | null>;
117
+ listPendingAsksBySession(sessionId: string): Promise<PendingAskRecord[]>;
118
+ listPendingAsksByRootSession(rootSessionId: string): Promise<PendingAskRecord[]>;
119
+
120
+ // Permission lifecycle (former tools/permission-request-manager.ts surface).
121
+ requestPermission(params: RequestPermissionParams): Promise<unknown>;
122
+ resolvePermission(requestId: string, response: unknown): Promise<boolean>;
123
+ rejectPermission(requestId: string, error: Error): boolean;
124
+ rejectPermissionsByToolCallId(toolCallId: string, error?: Error): Promise<string[]>;
125
+ rejectPermissionsBySession(sessionId: string, error?: Error): Promise<string[]>;
126
+ getPendingRequestsByRootSession(rootSessionId: string): Promise<PendingAskRecord[]>;
127
+ expireOldRequests(maxAgeMs: number): Promise<number>;
128
+ hasPendingWaiter(requestId: string): boolean;
129
+ getPendingWaiterCount(): number;
130
+ }
131
+
132
+ /** Capability-ask authority carrying the required client capability. */
133
+ export type CapabilityAskAuthority = AskAuthority & {
134
+ requiredCapabilities?: ClientCapability[];
135
+ };
@@ -0,0 +1,58 @@
1
+ /**
2
+ * C6 permission lifecycle surface over the NON-REPLACEABLE permission
3
+ * runtime. These exports preserve the exact pre-C6
4
+ * `tools/permission-request-manager.ts` identities; the waiters, timers,
5
+ * grant reuse, validation, and audit decisions resolve through
6
+ * `getPermissionRuntimeService()`.
7
+ *
8
+ * The runtime enforces the mandatory deny invariant from the module-level
9
+ * `isValidPermissionResponse` (never from provider advice), records the raw
10
+ * payload in the denied audit record (preserving the pre-C6 audit
11
+ * behavior), and persists only the canonical `buildGrantParams` output, so
12
+ * a replaced advice provider can never approve malformed responses or
13
+ * create grants outside the canonical allowed scopes.
14
+ */
15
+
16
+ import type { PendingAskRecord } from '../runtime/host';
17
+ import { getPermissionRuntimeService, } from './runtime';
18
+ import { PERMISSION_TIMEOUT } from './policy';
19
+ import type { PermissionBroadcastFn, RequestPermissionParams } from './contracts';
20
+
21
+ export { PERMISSION_TIMEOUT };
22
+ export type { PermissionBroadcastFn, RequestPermissionParams };
23
+
24
+ export function requestPermission(params: RequestPermissionParams): Promise<unknown> {
25
+ return getPermissionRuntimeService().requestPermission(params);
26
+ }
27
+
28
+ export async function resolvePermission(requestId: string, response: unknown): Promise<boolean> {
29
+ return getPermissionRuntimeService().resolvePermission(requestId, response);
30
+ }
31
+
32
+ export function rejectPermission(requestId: string, error: Error): boolean {
33
+ return getPermissionRuntimeService().rejectPermission(requestId, error);
34
+ }
35
+
36
+ export async function rejectPermissionsByToolCallId(toolCallId: string, error?: Error): Promise<string[]> {
37
+ return getPermissionRuntimeService().rejectPermissionsByToolCallId(toolCallId, error);
38
+ }
39
+
40
+ export async function rejectPermissionsBySession(sessionId: string, error?: Error): Promise<string[]> {
41
+ return getPermissionRuntimeService().rejectPermissionsBySession(sessionId, error);
42
+ }
43
+
44
+ export async function getPendingRequestsByRootSession(rootSessionId: string): Promise<PendingAskRecord[]> {
45
+ return getPermissionRuntimeService().getPendingRequestsByRootSession(rootSessionId);
46
+ }
47
+
48
+ export async function expireOldRequests(maxAgeMs: number): Promise<number> {
49
+ return getPermissionRuntimeService().expireOldRequests(maxAgeMs);
50
+ }
51
+
52
+ export function hasPendingWaiter(requestId: string): boolean {
53
+ return getPermissionRuntimeService().hasPendingWaiter(requestId);
54
+ }
55
+
56
+ export function getPendingWaiterCount(): number {
57
+ return getPermissionRuntimeService().getPendingWaiterCount();
58
+ }
@@ -0,0 +1,277 @@
1
+ /**
2
+ * C6 permission policy: REPLACEABLE advice and configuration only.
3
+ *
4
+ * The non-replaceable lifecycle (waiters, routing, validation enforcement,
5
+ * raw-audit denial, canonical grant construction and persistence) lives in
6
+ * `permission/runtime.ts`. This module owns:
7
+ *
8
+ * - The frozen provider options (generic ask timeout constant and the
9
+ * composition-translated permission timeout).
10
+ * - Authority resolution and value extraction for generic asks.
11
+ * - The module-level validators and canonical grant builder that BOTH the
12
+ * default provider and the runtime close over. A custom provider can
13
+ * override the advice methods on its instance, but the runtime never
14
+ * consults those overrides for mandatory decisions.
15
+ */
16
+
17
+ import { AsyncLocalStorage } from 'node:async_hooks';
18
+ import type { Ask } from '@capekai/tool';
19
+ import type {
20
+ AskAuthority, AskPermissionResponse, ClientCapability, GrantScope, PermissionIntent,
21
+ } from '@capekai/types';
22
+ import type { PermissionAsk, PermissionRiskLevel } from '@capekai/tool';
23
+ import { SHELL_FILESYSTEM_COMMANDS } from '@capekai/tool';
24
+ import { SHELL_DANGEROUS_COMMANDS } from '@capekai/tool';
25
+ import { getRuntimeHost } from '../runtime/host';
26
+ import type {
27
+ CreateGrantParams,
28
+ PendingAskRecord,
29
+ } from '../runtime/host';
30
+ import type {
31
+ AskPermissionPolicyOptions,
32
+ AskPermissionPolicyService,
33
+ } from './contracts';
34
+
35
+ export const ASK_TIMEOUT = 5 * 60 * 1000;
36
+
37
+ /** Legacy export from the pre-C6 permission-request-manager module. */
38
+ export const PERMISSION_TIMEOUT = 30 * 60 * 1000;
39
+
40
+ const DEFAULT_ASK_AUTHORITY: AskAuthority = {
41
+ visibilityScope: 'controller_only',
42
+ resolutionMode: 'controller_only',
43
+ };
44
+
45
+ const RISK_ORDER: PermissionRiskLevel[] = ['none', 'low', 'medium', 'high', 'critical'];
46
+ const VALID_GRANT_SCOPES: GrantScope[] = ['once', 'session', 'workspace'];
47
+
48
+ export interface AskPermissionServiceCreateOptions {
49
+ id?: string;
50
+ /** Frozen composition-time options. When omitted (the process-default
51
+ * fallback), the permission timeout resolves live from the installed
52
+ * interaction host exactly like the pre-C6 module code. */
53
+ options?: AskPermissionPolicyOptions;
54
+ }
55
+
56
+ function interaction() {
57
+ return getRuntimeHost().interaction;
58
+ }
59
+
60
+ function resolveOptionsLive(): AskPermissionPolicyOptions {
61
+ return {
62
+ askTimeoutMs: ASK_TIMEOUT,
63
+ permissionTimeoutMs: interaction().getPermissionTimeoutMs(),
64
+ };
65
+ }
66
+
67
+ // ── Module-level non-overridable validators and canonical grants ─────────
68
+
69
+ export function isRiskAtOrBelow(risk: PermissionRiskLevel, max: PermissionRiskLevel): boolean {
70
+ return RISK_ORDER.indexOf(risk) <= RISK_ORDER.indexOf(max);
71
+ }
72
+
73
+ export function isValidPermissionResponse(response: unknown): response is AskPermissionResponse {
74
+ if (!response || typeof response !== 'object') return false;
75
+ const record = response as Record<string, unknown>;
76
+ return record.type === 'permission' && VALID_GRANT_SCOPES.includes(record.grant as GrantScope);
77
+ }
78
+
79
+ export function isPermissionApproved(response: unknown): boolean {
80
+ return isValidPermissionResponse(response) && response.grant !== 'deny';
81
+ }
82
+
83
+ export function buildPermissionKey(
84
+ toolName: string,
85
+ resource: string | undefined,
86
+ patterns: string[] | undefined,
87
+ ): string {
88
+ if (patterns && patterns.length > 0) return patterns[0];
89
+ if (resource) return resource;
90
+ return toolName;
91
+ }
92
+
93
+ export function isDangerousShellIdentity(identity: string | undefined): boolean {
94
+ if (!identity) return false;
95
+ const lower = identity.toLowerCase();
96
+ const dangerous = SHELL_DANGEROUS_COMMANDS.some(
97
+ command => lower === command || lower.startsWith(command + ' '),
98
+ );
99
+ if (dangerous) return true;
100
+ return SHELL_FILESYSTEM_COMMANDS.some(
101
+ command => lower === command || lower.startsWith(command + ' '),
102
+ );
103
+ }
104
+
105
+ /** Canonical grant construction: the only grant-shape source the runtime
106
+ * persists. Enforces allowed scopes, the session-duration default, the
107
+ * root binding, and the dangerous-shell once downgrade. */
108
+ export function buildGrantParams(
109
+ record: PendingAskRecord,
110
+ response: AskPermissionResponse,
111
+ ): CreateGrantParams[] {
112
+ if (!record.workspaceId || response.grant === 'deny') return [];
113
+ const permAsk = record.ask as PermissionAsk;
114
+ let grantScope: GrantScope = response.grant;
115
+ const boundRootSessionId = record.rootSessionId ?? record.sessionId;
116
+
117
+ if (permAsk.intents && permAsk.intents.length > 0) {
118
+ const intent: PermissionIntent = permAsk.intents[0];
119
+ if (!intent.allowedScopes.includes(grantScope) || grantScope === 'once') return [];
120
+ const duration = response.duration || (grantScope === 'session' ? 30 * 60 * 1000 : undefined);
121
+
122
+ return intent.targets.map((target) => ({
123
+ workspaceId: record.workspaceId!,
124
+ toolName: record.toolName,
125
+ resource: intent.resource,
126
+ action: intent.action,
127
+ permissionKey: target.target,
128
+ grantOptions: {
129
+ scope: grantScope,
130
+ matcher: target.matcher === 'prefix' ? 'prefix' : 'exact',
131
+ patterns: [target.target],
132
+ action: intent.action,
133
+ duration: grantScope === 'session' ? duration : undefined,
134
+ description: permAsk.question,
135
+ boundRootSessionId: grantScope === 'session' ? boundRootSessionId : undefined,
136
+ },
137
+ }));
138
+ }
139
+
140
+ const metadata = permAsk.metadata as Record<string, unknown> | undefined;
141
+ const identity = typeof metadata?.baseCommand === 'string' ? metadata.baseCommand : undefined;
142
+ if (permAsk.resource === 'shell-command' && isDangerousShellIdentity(identity)) {
143
+ grantScope = 'once';
144
+ }
145
+ const duration = response.duration || (grantScope === 'session' ? 30 * 60 * 1000 : undefined);
146
+ return [{
147
+ workspaceId: record.workspaceId,
148
+ toolName: record.toolName,
149
+ resource: permAsk.resource ?? 'file',
150
+ action: permAsk.action,
151
+ permissionKey: buildPermissionKey(record.toolName, permAsk.resource ?? 'file', permAsk.patterns),
152
+ grantOptions: {
153
+ scope: grantScope,
154
+ matcher: (permAsk.resource ?? 'file') === 'shell-command' ? 'shell-command' : 'exact',
155
+ patterns: permAsk.patterns,
156
+ action: permAsk.action,
157
+ duration: grantScope === 'session' ? duration : undefined,
158
+ description: permAsk.question,
159
+ boundRootSessionId: grantScope === 'session' ? boundRootSessionId : undefined,
160
+ },
161
+ }];
162
+ }
163
+
164
+ export function resolveAskAuthority(request: Ask): AskAuthority {
165
+ if (
166
+ request.type === 'client_capability'
167
+ && 'target' in request
168
+ && request.target === 'client'
169
+ ) {
170
+ return {
171
+ visibilityScope: 'global',
172
+ resolutionMode: 'first_eligible',
173
+ requiredCapabilities: [request.capability as ClientCapability],
174
+ };
175
+ }
176
+ return DEFAULT_ASK_AUTHORITY;
177
+ }
178
+
179
+ export function extractResolutionValue(response: unknown): unknown {
180
+ if (!response || typeof response !== 'object') return response;
181
+ const record = response as Record<string, unknown>;
182
+
183
+ switch (record.type) {
184
+ case 'single_select':
185
+ return record.value;
186
+ case 'multi_select':
187
+ return record.values;
188
+ case 'text':
189
+ return record.value;
190
+ case 'confirm':
191
+ return record.confirmed;
192
+ case 'form':
193
+ return response;
194
+ case 'client_capability':
195
+ return record.result;
196
+ default:
197
+ return response;
198
+ }
199
+ }
200
+
201
+ /** The C6 default advice provider wrapping the exact pre-C6 behavior. */
202
+ export function createAskPermissionService(
203
+ createOptions: AskPermissionServiceCreateOptions = {},
204
+ ): AskPermissionPolicyService {
205
+ const id = createOptions.id ?? 'permission.default';
206
+ const frozenOptions = createOptions.options;
207
+
208
+ function options(): AskPermissionPolicyOptions {
209
+ return frozenOptions ?? resolveOptionsLive();
210
+ }
211
+
212
+ return {
213
+ id,
214
+ get options(): Readonly<AskPermissionPolicyOptions> {
215
+ return options();
216
+ },
217
+ get askTimeoutMs(): number {
218
+ return options().askTimeoutMs;
219
+ },
220
+ get permissionTimeoutMs(): number {
221
+ return options().permissionTimeoutMs;
222
+ },
223
+ resolveAskAuthority,
224
+ extractResolutionValue,
225
+ isValidPermissionResponse,
226
+ isPermissionApproved,
227
+ isRiskAtOrBelow,
228
+ async shouldAutoApprove(sessionId: string, ask: Ask): Promise<boolean> {
229
+ if (ask.type !== 'permission') return false;
230
+ const risk = (ask as PermissionAsk).risk;
231
+ if (!risk) return false;
232
+
233
+ const maxSeverity = await interaction().getSessionAutoApproveSeverity(sessionId);
234
+ if (!maxSeverity || maxSeverity === 'off') return false;
235
+ if (!isRiskAtOrBelow(risk, maxSeverity)) return false;
236
+
237
+ console.log(
238
+ `[permissions] Server auto-approve: risk=${risk} maxSeverity=${maxSeverity} session=${sessionId}`,
239
+ );
240
+ return true;
241
+ },
242
+ buildPermissionKey,
243
+ isDangerousShellIdentity,
244
+ };
245
+ }
246
+
247
+ const scopedPolicy = new AsyncLocalStorage<AskPermissionPolicyService>();
248
+ let processDefaultPolicy: AskPermissionPolicyService | undefined;
249
+
250
+ /** Resolves the service seeded for the active agent scope, falling back to
251
+ * one lazily created process-default service for consumers that run outside
252
+ * a composed scope (the current Jean2 server path). */
253
+ export function getAskPermissionPolicy(): AskPermissionPolicyService {
254
+ return scopedPolicy.getStore()
255
+ ?? (processDefaultPolicy ??= createAskPermissionService({ id: 'permission.process-default' }));
256
+ }
257
+
258
+ /** The scoped policy only (undefined outside a composed scope). Used by the
259
+ * runtime to prefer the actively seeded advice provider. */
260
+ export function getActiveAskPermissionPolicy(): AskPermissionPolicyService | undefined {
261
+ return scopedPolicy.getStore();
262
+ }
263
+
264
+ /** Seeds a service for the callback duration. `enterAgentScope` seeds the
265
+ * composed agent scope's service here. */
266
+ export function withAskPermissionPolicy<T>(
267
+ service: AskPermissionPolicyService,
268
+ callback: () => T,
269
+ ): T {
270
+ return scopedPolicy.run(service, callback);
271
+ }
272
+
273
+ /** Test-only reset of the lazily created process default. Exported from this
274
+ * module only; no package subpath re-exports it. */
275
+ export function resetDefaultAskPermissionPolicyForTests(): void {
276
+ processDefaultPolicy = undefined;
277
+ }