@inixiative/foundry-core 0.2.0 → 0.4.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.
- package/package.json +1 -6
- package/src/action-prompt.ts +47 -28
- package/src/adapters/file-memory.ts +314 -122
- package/src/adapters/http-memory.ts +19 -24
- package/src/adapters/index.ts +4 -4
- package/src/adapters/markdown-docs.ts +15 -15
- package/src/adapters/sqlite-memory.ts +29 -27
- package/src/base-agent.ts +5 -7
- package/src/cache-lifecycle.ts +14 -14
- package/src/capability.ts +76 -83
- package/src/clarifier.ts +46 -0
- package/src/classifier.ts +6 -9
- package/src/context-layer.ts +125 -78
- package/src/context-stack.ts +15 -14
- package/src/credentials.ts +4 -1
- package/src/decider.ts +7 -8
- package/src/delivery-evidence.ts +206 -55
- package/src/event-stream.ts +34 -20
- package/src/executor.ts +81 -43
- package/src/harness.ts +225 -126
- package/src/hooks.ts +26 -26
- package/src/hydrator.ts +6 -8
- package/src/id.ts +3 -3
- package/src/index.ts +252 -218
- package/src/intervention.ts +21 -12
- package/src/messages.ts +73 -62
- package/src/middleware.ts +9 -19
- package/src/model-registry.ts +1645 -0
- package/src/native-evidence.ts +124 -30
- package/src/permission-middleware.ts +16 -28
- package/src/retry.ts +6 -6
- package/src/router.ts +5 -5
- package/src/signal.ts +16 -11
- package/src/thread.ts +138 -61
- package/src/token-counts.ts +9 -1
- package/src/token-tracker.ts +43 -34
- package/src/tools.ts +138 -95
- package/src/trace.ts +44 -23
- package/src/types.ts +33 -24
package/src/capability.ts
CHANGED
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
// a human (or policy hook) approves.
|
|
16
16
|
// ---------------------------------------------------------------------------
|
|
17
17
|
|
|
18
|
-
import { ActionQueue,
|
|
18
|
+
import type { ActionQueue, ActionResolution } from './action-prompt';
|
|
19
19
|
|
|
20
20
|
// ---------------------------------------------------------------------------
|
|
21
21
|
// Built-in capabilities
|
|
@@ -27,34 +27,34 @@ import { ActionQueue, type ActionResolution } from "./action-prompt";
|
|
|
27
27
|
*/
|
|
28
28
|
export type BuiltinCapability =
|
|
29
29
|
// File system
|
|
30
|
-
|
|
|
31
|
-
|
|
|
32
|
-
|
|
|
30
|
+
| 'file:read'
|
|
31
|
+
| 'file:write'
|
|
32
|
+
| 'file:delete'
|
|
33
33
|
// Network
|
|
34
|
-
|
|
|
35
|
-
|
|
|
34
|
+
| 'net:fetch'
|
|
35
|
+
| 'net:api'
|
|
36
36
|
// LLM
|
|
37
|
-
|
|
|
38
|
-
|
|
|
37
|
+
| 'llm:call'
|
|
38
|
+
| 'llm:expensive'
|
|
39
39
|
// Data stores
|
|
40
|
-
|
|
|
41
|
-
|
|
|
42
|
-
|
|
|
40
|
+
| 'data:read'
|
|
41
|
+
| 'data:write'
|
|
42
|
+
| 'data:delete'
|
|
43
43
|
// Execution
|
|
44
|
-
|
|
|
45
|
-
|
|
|
44
|
+
| 'exec:shell'
|
|
45
|
+
| 'exec:process'
|
|
46
46
|
// Browser
|
|
47
|
-
|
|
|
48
|
-
|
|
|
49
|
-
|
|
|
50
|
-
|
|
|
47
|
+
| 'browser:navigate'
|
|
48
|
+
| 'browser:interact'
|
|
49
|
+
| 'browser:execute'
|
|
50
|
+
| 'browser:screenshot'
|
|
51
51
|
// Meta
|
|
52
|
-
|
|
|
52
|
+
| 'prompt:auto-resolve';
|
|
53
53
|
|
|
54
54
|
/** Any string is a valid capability. Builtins are just conventions. */
|
|
55
55
|
export type Capability = BuiltinCapability | (string & {});
|
|
56
56
|
|
|
57
|
-
export type PermissionLevel =
|
|
57
|
+
export type PermissionLevel = 'allow' | 'prompt' | 'deny';
|
|
58
58
|
|
|
59
59
|
// ---------------------------------------------------------------------------
|
|
60
60
|
// PermissionPolicy
|
|
@@ -71,59 +71,42 @@ export interface PermissionPolicy {
|
|
|
71
71
|
protectedPaths?: string[];
|
|
72
72
|
}
|
|
73
73
|
|
|
74
|
-
/** Convenience: everything allowed, no prompts. */
|
|
75
|
-
export const UNATTENDED_POLICY: PermissionPolicy = {
|
|
76
|
-
defaults: "allow",
|
|
77
|
-
capabilities: {},
|
|
78
|
-
};
|
|
79
|
-
|
|
80
|
-
/** Convenience: prompt for writes/deletes/exec/expensive, allow reads. */
|
|
81
|
-
export const SUPERVISED_POLICY: PermissionPolicy = {
|
|
82
|
-
defaults: "prompt",
|
|
83
|
-
capabilities: {
|
|
84
|
-
"file:read": "allow",
|
|
85
|
-
"data:read": "allow",
|
|
86
|
-
"net:fetch": "allow",
|
|
87
|
-
"llm:call": "allow",
|
|
88
|
-
},
|
|
89
|
-
};
|
|
90
|
-
|
|
91
74
|
/** Convenience: deny dangerous ops, allow reads and cheap LLM. */
|
|
92
75
|
export const RESTRICTED_POLICY: PermissionPolicy = {
|
|
93
|
-
defaults:
|
|
76
|
+
defaults: 'deny',
|
|
94
77
|
capabilities: {
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
78
|
+
'file:read': 'allow',
|
|
79
|
+
'data:read': 'allow',
|
|
80
|
+
'net:fetch': 'allow',
|
|
81
|
+
'llm:call': 'allow',
|
|
82
|
+
'llm:expensive': 'deny',
|
|
83
|
+
'exec:shell': 'deny',
|
|
84
|
+
'exec:process': 'deny',
|
|
85
|
+
'file:delete': 'deny',
|
|
86
|
+
'data:delete': 'deny',
|
|
87
|
+
'browser:navigate': 'deny',
|
|
88
|
+
'browser:interact': 'deny',
|
|
89
|
+
'browser:execute': 'deny',
|
|
90
|
+
'browser:screenshot': 'deny',
|
|
108
91
|
},
|
|
109
92
|
};
|
|
110
93
|
|
|
111
94
|
/** Convenience: browser-capable agent — allows navigation and interaction, prompts for JS execution. */
|
|
112
95
|
export const BROWSER_POLICY: PermissionPolicy = {
|
|
113
|
-
defaults:
|
|
96
|
+
defaults: 'prompt',
|
|
114
97
|
capabilities: {
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
98
|
+
'file:read': 'allow',
|
|
99
|
+
'data:read': 'allow',
|
|
100
|
+
'net:fetch': 'allow',
|
|
101
|
+
'llm:call': 'allow',
|
|
102
|
+
'browser:navigate': 'allow',
|
|
103
|
+
'browser:interact': 'allow',
|
|
104
|
+
'browser:execute': 'prompt',
|
|
105
|
+
'browser:screenshot': 'allow',
|
|
106
|
+
'exec:shell': 'deny',
|
|
107
|
+
'exec:process': 'deny',
|
|
108
|
+
'file:delete': 'deny',
|
|
109
|
+
'data:delete': 'deny',
|
|
127
110
|
},
|
|
128
111
|
};
|
|
129
112
|
|
|
@@ -136,7 +119,7 @@ export class CapabilityDeniedError extends Error {
|
|
|
136
119
|
readonly level: PermissionLevel;
|
|
137
120
|
constructor(capability: string, level: PermissionLevel) {
|
|
138
121
|
super(`Capability "${capability}" is ${level}`);
|
|
139
|
-
this.name =
|
|
122
|
+
this.name = 'CapabilityDeniedError';
|
|
140
123
|
this.capability = capability;
|
|
141
124
|
this.level = level;
|
|
142
125
|
}
|
|
@@ -159,9 +142,11 @@ export class CapabilityGate {
|
|
|
159
142
|
this._queue = queue;
|
|
160
143
|
}
|
|
161
144
|
|
|
162
|
-
get policy(): PermissionPolicy {
|
|
145
|
+
get policy(): PermissionPolicy {
|
|
146
|
+
return this._policy;
|
|
147
|
+
}
|
|
163
148
|
|
|
164
|
-
/** Update policy at runtime
|
|
149
|
+
/** Update policy at runtime. */
|
|
165
150
|
setPolicy(policy: PermissionPolicy): void {
|
|
166
151
|
this._policy = policy;
|
|
167
152
|
}
|
|
@@ -173,18 +158,18 @@ export class CapabilityGate {
|
|
|
173
158
|
async check(capability: Capability, ctx: GateContext): Promise<ActionResolution> {
|
|
174
159
|
const level = this._resolve(capability, ctx);
|
|
175
160
|
|
|
176
|
-
if (level ===
|
|
177
|
-
return { by:
|
|
161
|
+
if (level === 'allow') {
|
|
162
|
+
return { by: 'policy', action: 'approved', timestamp: Date.now() };
|
|
178
163
|
}
|
|
179
164
|
|
|
180
|
-
if (level ===
|
|
181
|
-
return { by:
|
|
165
|
+
if (level === 'deny') {
|
|
166
|
+
return { by: 'policy', action: 'rejected', timestamp: Date.now() };
|
|
182
167
|
}
|
|
183
168
|
|
|
184
169
|
// level === "prompt"
|
|
185
170
|
const message = this._buildMessage(capability, ctx);
|
|
186
171
|
return this._queue.prompt({
|
|
187
|
-
kind:
|
|
172
|
+
kind: 'approval',
|
|
188
173
|
message,
|
|
189
174
|
agentId: ctx.agentId,
|
|
190
175
|
threadId: ctx.threadId,
|
|
@@ -200,8 +185,8 @@ export class CapabilityGate {
|
|
|
200
185
|
*/
|
|
201
186
|
async require(capability: Capability, ctx: GateContext): Promise<void> {
|
|
202
187
|
const resolution = await this.check(capability, ctx);
|
|
203
|
-
if (resolution.action !==
|
|
204
|
-
throw new CapabilityDeniedError(capability,
|
|
188
|
+
if (resolution.action !== 'approved') {
|
|
189
|
+
throw new CapabilityDeniedError(capability, 'deny');
|
|
205
190
|
}
|
|
206
191
|
}
|
|
207
192
|
|
|
@@ -221,17 +206,25 @@ export class CapabilityGate {
|
|
|
221
206
|
if (explicit) return explicit;
|
|
222
207
|
|
|
223
208
|
// Cost threshold check for LLM calls
|
|
224
|
-
if (
|
|
209
|
+
if (
|
|
210
|
+
capability === 'llm:call' &&
|
|
211
|
+
this._policy.costThreshold != null &&
|
|
212
|
+
ctx?.meta?.estimatedCost != null
|
|
213
|
+
) {
|
|
225
214
|
if ((ctx.meta.estimatedCost as number) > this._policy.costThreshold) {
|
|
226
|
-
return
|
|
215
|
+
return 'prompt';
|
|
227
216
|
}
|
|
228
217
|
}
|
|
229
218
|
|
|
230
219
|
// Protected paths check for file ops
|
|
231
|
-
if (
|
|
220
|
+
if (
|
|
221
|
+
(capability === 'file:write' || capability === 'file:delete') &&
|
|
222
|
+
this._policy.protectedPaths?.length &&
|
|
223
|
+
ctx?.meta?.path
|
|
224
|
+
) {
|
|
232
225
|
const path = ctx.meta.path as string;
|
|
233
226
|
if (this._policy.protectedPaths.some((p) => path.includes(p))) {
|
|
234
|
-
return
|
|
227
|
+
return 'prompt';
|
|
235
228
|
}
|
|
236
229
|
}
|
|
237
230
|
|
|
@@ -239,14 +232,14 @@ export class CapabilityGate {
|
|
|
239
232
|
}
|
|
240
233
|
|
|
241
234
|
private _buildMessage(capability: Capability, ctx: GateContext): string {
|
|
242
|
-
const detail = ctx.detail ? ` — ${ctx.detail}` :
|
|
235
|
+
const detail = ctx.detail ? ` — ${ctx.detail}` : '';
|
|
243
236
|
return `Agent "${ctx.agentId}" requires capability "${capability}"${detail}`;
|
|
244
237
|
}
|
|
245
238
|
|
|
246
|
-
private _urgencyFor(capability: Capability):
|
|
247
|
-
if (capability ===
|
|
248
|
-
if (capability.endsWith(
|
|
249
|
-
if (capability ===
|
|
250
|
-
return
|
|
239
|
+
private _urgencyFor(capability: Capability): 'low' | 'normal' | 'high' | 'critical' {
|
|
240
|
+
if (capability === 'exec:shell' || capability === 'exec:process') return 'high';
|
|
241
|
+
if (capability.endsWith(':delete')) return 'high';
|
|
242
|
+
if (capability === 'llm:expensive') return 'normal';
|
|
243
|
+
return 'normal';
|
|
251
244
|
}
|
|
252
245
|
}
|
package/src/clarifier.ts
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import type { AgentConfig } from './base-agent';
|
|
2
|
+
import type { Classification } from './classifier';
|
|
3
|
+
import type { ContextStack } from './context-stack';
|
|
4
|
+
import { Decider, type DeciderConfig, type Decision } from './decider';
|
|
5
|
+
import type { ExecuteMeta } from './executor';
|
|
6
|
+
|
|
7
|
+
/** The raw message plus the classification already computed upstream. */
|
|
8
|
+
export interface ClarifyPayload {
|
|
9
|
+
readonly message: string;
|
|
10
|
+
readonly classification: Classification;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/** needed=true: the request is underspecified and `questions` says what is missing. */
|
|
14
|
+
export interface ClarificationResult {
|
|
15
|
+
readonly needed: boolean;
|
|
16
|
+
readonly questions?: string[];
|
|
17
|
+
readonly reasoning?: string;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export type ClarifyHandler = (
|
|
21
|
+
context: string,
|
|
22
|
+
payload: ClarifyPayload,
|
|
23
|
+
meta?: ExecuteMeta,
|
|
24
|
+
) => Promise<Decision<ClarificationResult>>;
|
|
25
|
+
|
|
26
|
+
export interface ClarifierConfig extends AgentConfig {
|
|
27
|
+
handler: ClarifyHandler;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* A Clarifier is a Decider that checks whether a request is complete enough
|
|
32
|
+
* to execute. Its context should hold only a compact completeness schema
|
|
33
|
+
* (category -> required slots); pair it with a fast model.
|
|
34
|
+
*
|
|
35
|
+
* Sits between "route" and "execute". When needed=true the harness
|
|
36
|
+
* short-circuits and returns the questions instead of dispatching execution.
|
|
37
|
+
*/
|
|
38
|
+
export class Clarifier extends Decider<ClarifyPayload, ClarificationResult> {
|
|
39
|
+
constructor(config: ClarifierConfig) {
|
|
40
|
+
super(config as DeciderConfig<ClarifyPayload, ClarificationResult>);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
override withStack(stack: ContextStack): Clarifier {
|
|
44
|
+
return new Clarifier({ ...this.agentConfig(stack), handler: this._handler });
|
|
45
|
+
}
|
|
46
|
+
}
|
package/src/classifier.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import
|
|
2
|
-
import type {
|
|
3
|
-
import type
|
|
4
|
-
import type { ExecuteMeta } from
|
|
1
|
+
import type { AgentConfig } from './base-agent';
|
|
2
|
+
import type { ContextStack } from './context-stack';
|
|
3
|
+
import { Decider, type DeciderConfig, type Decision } from './decider';
|
|
4
|
+
import type { ExecuteMeta } from './executor';
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* A classification — "what is this thing?"
|
|
@@ -15,7 +15,7 @@ export interface Classification {
|
|
|
15
15
|
export type ClassifyHandler<TPayload> = (
|
|
16
16
|
context: string,
|
|
17
17
|
payload: TPayload,
|
|
18
|
-
meta?: ExecuteMeta
|
|
18
|
+
meta?: ExecuteMeta,
|
|
19
19
|
) => Promise<Decision<Classification>>;
|
|
20
20
|
|
|
21
21
|
export interface ClassifierConfig<TPayload = unknown> extends AgentConfig {
|
|
@@ -29,10 +29,7 @@ export interface ClassifierConfig<TPayload = unknown> extends AgentConfig {
|
|
|
29
29
|
* The Classifier has the full taxonomy loaded in its context layers.
|
|
30
30
|
* The caller sends a payload and gets back a category — not the taxonomy.
|
|
31
31
|
*/
|
|
32
|
-
export class Classifier<TPayload = unknown> extends Decider<
|
|
33
|
-
TPayload,
|
|
34
|
-
Classification
|
|
35
|
-
> {
|
|
32
|
+
export class Classifier<TPayload = unknown> extends Decider<TPayload, Classification> {
|
|
36
33
|
constructor(config: ClassifierConfig<TPayload>) {
|
|
37
34
|
super(config as DeciderConfig<TPayload, Classification>);
|
|
38
35
|
}
|