@hobin/developer 0.1.3 → 0.1.5
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 +160 -38
- package/extensions/developer.ts +483 -68
- package/extensions/machine.ts +344 -0
- package/extensions/state.ts +128 -98
- package/extensions/tool-policy.ts +54 -25
- package/extensions/tui.ts +407 -202
- package/package.json +7 -2
- package/skills/abstraction-review/SKILL.md +6 -5
- package/skills/adversarial-eval/SKILL.md +7 -3
- package/skills/model/SKILL.md +13 -4
- package/skills/naming-judgment/SKILL.md +9 -3
- package/skills/schedule/SKILL.md +6 -3
- package/skills/signal/SKILL.md +6 -3
- package/skills/sketch/SKILL.md +29 -10
- package/skills/specify/SKILL.md +11 -2
- package/skills/verify/SKILL.md +10 -4
- package/skills/visualize/SKILL.md +5 -3
package/extensions/state.ts
CHANGED
|
@@ -1,5 +1,16 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
import { applyDeveloperEvent, developerSnapshot, initialState } from "./machine.ts";
|
|
2
|
+
|
|
3
|
+
export {
|
|
4
|
+
applyDeveloperEvent,
|
|
5
|
+
canApplyDeveloperEvent,
|
|
6
|
+
developerMachine,
|
|
7
|
+
developerSnapshot,
|
|
8
|
+
initialState,
|
|
9
|
+
} from "./machine.ts";
|
|
10
|
+
|
|
11
|
+
export const PROTOCOL = "developer/v4" as const;
|
|
12
|
+
export const PREVIOUS_PROTOCOL = "developer/v3" as const;
|
|
13
|
+
export const OLDER_PROTOCOL = "developer/v2" as const;
|
|
3
14
|
export const LEGACY_PROTOCOL = "developer/v1" as const;
|
|
4
15
|
export const MODE_ENTRY = "developer.mode" as const;
|
|
5
16
|
export const FOCUS_ENTRY = "developer.question-focus" as const;
|
|
@@ -10,7 +21,10 @@ export const LEGACY_JUDGMENT_TOOL = "record_judgment" as const;
|
|
|
10
21
|
|
|
11
22
|
export type DeveloperMode = "off" | "on" | "strict";
|
|
12
23
|
export type JudgmentStatus = "resolved" | "needs-evidence" | "not-applicable" | "blocked";
|
|
13
|
-
export type PendingQuestionStatus = "
|
|
24
|
+
export type PendingQuestionStatus = "open" | "blocked";
|
|
25
|
+
export type QuestionResolutionOwner = "agent" | "user" | "environment" | "unknown";
|
|
26
|
+
export type QuestionGate = "none" | "before-direct" | "before-completion";
|
|
27
|
+
export type QuestionUpdateStatus = "resolved" | "not-applicable" | "open" | "blocked";
|
|
14
28
|
export type DirectExecutionProfile = "ordinary" | "behavior-preserving-structure";
|
|
15
29
|
|
|
16
30
|
export interface ModeEvent {
|
|
@@ -31,6 +45,11 @@ export interface DirectStepContract {
|
|
|
31
45
|
verification: string;
|
|
32
46
|
}
|
|
33
47
|
|
|
48
|
+
export interface RouteAlternative {
|
|
49
|
+
owner: string;
|
|
50
|
+
reason: string;
|
|
51
|
+
}
|
|
52
|
+
|
|
34
53
|
export interface RouteEvent {
|
|
35
54
|
protocol: typeof PROTOCOL;
|
|
36
55
|
kind: "route";
|
|
@@ -39,6 +58,7 @@ export interface RouteEvent {
|
|
|
39
58
|
owner: string;
|
|
40
59
|
reason: string;
|
|
41
60
|
knownEvidence: string[];
|
|
61
|
+
consideredAlternatives: RouteAlternative[];
|
|
42
62
|
targetQuestionId?: string;
|
|
43
63
|
methodLocation?: string;
|
|
44
64
|
executionProfile?: DirectExecutionProfile;
|
|
@@ -49,9 +69,19 @@ export interface PendingQuestion {
|
|
|
49
69
|
id: string;
|
|
50
70
|
question: string;
|
|
51
71
|
status: PendingQuestionStatus;
|
|
72
|
+
resolutionOwner: QuestionResolutionOwner;
|
|
73
|
+
gate: QuestionGate;
|
|
74
|
+
resolutionCriteria: string;
|
|
52
75
|
sourceRouteId: string;
|
|
53
76
|
}
|
|
54
77
|
|
|
78
|
+
export interface QuestionUpdate {
|
|
79
|
+
questionId: string;
|
|
80
|
+
status: QuestionUpdateStatus;
|
|
81
|
+
result: string;
|
|
82
|
+
basis: string[];
|
|
83
|
+
}
|
|
84
|
+
|
|
55
85
|
export interface JudgmentEvent {
|
|
56
86
|
protocol: typeof PROTOCOL;
|
|
57
87
|
kind: "judgment";
|
|
@@ -62,6 +92,7 @@ export interface JudgmentEvent {
|
|
|
62
92
|
result: string;
|
|
63
93
|
basis: string[];
|
|
64
94
|
openedQuestions: PendingQuestion[];
|
|
95
|
+
questionUpdates: QuestionUpdate[];
|
|
65
96
|
artifacts: string[];
|
|
66
97
|
changedArtifacts: boolean;
|
|
67
98
|
}
|
|
@@ -73,108 +104,38 @@ export interface DeveloperState {
|
|
|
73
104
|
activeRoute?: RouteEvent;
|
|
74
105
|
lastRoute?: RouteEvent;
|
|
75
106
|
lastJudgment?: JudgmentEvent;
|
|
107
|
+
routeHistory: RouteEvent[];
|
|
108
|
+
judgmentHistory: JudgmentEvent[];
|
|
76
109
|
pendingQuestions: PendingQuestion[];
|
|
77
110
|
focusedQuestionId?: string;
|
|
111
|
+
rerouteRequired: boolean;
|
|
78
112
|
implementationFramingRequired: boolean;
|
|
79
113
|
verificationRequired: boolean;
|
|
80
114
|
}
|
|
81
115
|
|
|
82
|
-
export
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
116
|
+
export type ProtocolState =
|
|
117
|
+
| "idle"
|
|
118
|
+
| "needs-judgment"
|
|
119
|
+
| "needs-evidence"
|
|
120
|
+
| "needs-answer"
|
|
121
|
+
| "needs-routing"
|
|
122
|
+
| "needs-verification"
|
|
123
|
+
| "blocked";
|
|
90
124
|
|
|
91
125
|
export function protocolState(state: DeveloperState): ProtocolState {
|
|
92
|
-
|
|
93
|
-
if (
|
|
94
|
-
if (
|
|
95
|
-
|
|
126
|
+
const snapshot = developerSnapshot(state);
|
|
127
|
+
if (!snapshot.matches({ route: "idle" })) return "needs-judgment";
|
|
128
|
+
if (
|
|
129
|
+
snapshot.hasTag("blocks-direct") ||
|
|
130
|
+
state.pendingQuestions.some((question) => question.status === "blocked")
|
|
131
|
+
) return "blocked";
|
|
132
|
+
if (state.pendingQuestions.some((question) => question.resolutionOwner === "user")) return "needs-answer";
|
|
133
|
+
if (snapshot.matches({ questions: "open" })) return "needs-evidence";
|
|
134
|
+
if (snapshot.hasTag("reroute-required")) return "needs-routing";
|
|
135
|
+
if (snapshot.hasTag("verification-required")) return "needs-verification";
|
|
96
136
|
return "idle";
|
|
97
137
|
}
|
|
98
138
|
|
|
99
|
-
function upsertQuestion(questions: PendingQuestion[], next: PendingQuestion): PendingQuestion[] {
|
|
100
|
-
return [...questions.filter((question) => question.id !== next.id), next];
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
export function applyDeveloperEvent(state: DeveloperState, event: DeveloperEvent): DeveloperState {
|
|
104
|
-
if (event.kind === "mode") {
|
|
105
|
-
if (event.mode === "off") return initialState();
|
|
106
|
-
return { ...state, mode: event.mode };
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
if (event.kind === "focus") {
|
|
110
|
-
if (!state.pendingQuestions.some((question) => question.id === event.questionId)) return state;
|
|
111
|
-
return { ...state, focusedQuestionId: event.questionId };
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
if (event.kind === "route") {
|
|
115
|
-
if (state.activeRoute) return state;
|
|
116
|
-
return {
|
|
117
|
-
...state,
|
|
118
|
-
activeRoute: event,
|
|
119
|
-
lastRoute: event,
|
|
120
|
-
focusedQuestionId:
|
|
121
|
-
event.targetQuestionId === state.focusedQuestionId ? undefined : state.focusedQuestionId,
|
|
122
|
-
};
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
if (!state.activeRoute || state.activeRoute.routeId !== event.routeId) return state;
|
|
126
|
-
|
|
127
|
-
const route = state.activeRoute;
|
|
128
|
-
const judgment = { ...event, question: route.question, owner: route.owner };
|
|
129
|
-
let pending = [...state.pendingQuestions];
|
|
130
|
-
const remainsOpen = event.status === "needs-evidence" || event.status === "blocked";
|
|
131
|
-
|
|
132
|
-
if (route.targetQuestionId) {
|
|
133
|
-
pending = pending.filter((question) => question.id !== route.targetQuestionId);
|
|
134
|
-
if (remainsOpen) {
|
|
135
|
-
pending = upsertQuestion(pending, {
|
|
136
|
-
id: route.targetQuestionId,
|
|
137
|
-
question: route.question,
|
|
138
|
-
status: event.status === "blocked" ? "blocked" : "needs-evidence",
|
|
139
|
-
sourceRouteId: route.routeId,
|
|
140
|
-
});
|
|
141
|
-
}
|
|
142
|
-
} else if (remainsOpen) {
|
|
143
|
-
pending = upsertQuestion(pending, {
|
|
144
|
-
id: `question:${route.routeId}`,
|
|
145
|
-
question: route.question,
|
|
146
|
-
status: event.status === "blocked" ? "blocked" : "needs-evidence",
|
|
147
|
-
sourceRouteId: route.routeId,
|
|
148
|
-
});
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
for (const question of event.openedQuestions) pending = upsertQuestion(pending, question);
|
|
152
|
-
|
|
153
|
-
const closesFramingGate =
|
|
154
|
-
(route.owner === "sketch" || route.owner === "signal") &&
|
|
155
|
-
(event.status === "resolved" || event.status === "not-applicable");
|
|
156
|
-
const opensFramingGate = route.owner === "model" && event.status === "resolved";
|
|
157
|
-
let implementationFramingRequired = state.implementationFramingRequired;
|
|
158
|
-
if (opensFramingGate) implementationFramingRequired = true;
|
|
159
|
-
if (closesFramingGate) implementationFramingRequired = false;
|
|
160
|
-
|
|
161
|
-
let verificationRequired = state.verificationRequired;
|
|
162
|
-
if (route.owner === "direct" && event.changedArtifacts) verificationRequired = true;
|
|
163
|
-
if (route.owner === "verify" && event.status === "resolved") verificationRequired = false;
|
|
164
|
-
|
|
165
|
-
return {
|
|
166
|
-
...state,
|
|
167
|
-
activeRoute: undefined,
|
|
168
|
-
lastJudgment: judgment,
|
|
169
|
-
pendingQuestions: pending,
|
|
170
|
-
focusedQuestionId: pending.some((question) => question.id === state.focusedQuestionId)
|
|
171
|
-
? state.focusedQuestionId
|
|
172
|
-
: undefined,
|
|
173
|
-
implementationFramingRequired,
|
|
174
|
-
verificationRequired,
|
|
175
|
-
};
|
|
176
|
-
}
|
|
177
|
-
|
|
178
139
|
function isObject(value: unknown): value is Record<string, unknown> {
|
|
179
140
|
return Boolean(value) && typeof value === "object";
|
|
180
141
|
}
|
|
@@ -211,29 +172,79 @@ function parseDirectStep(value: unknown): DirectStepContract | undefined {
|
|
|
211
172
|
};
|
|
212
173
|
}
|
|
213
174
|
|
|
175
|
+
function parseRouteAlternative(value: unknown): RouteAlternative | undefined {
|
|
176
|
+
if (!isObject(value) || typeof value.owner !== "string" || typeof value.reason !== "string") {
|
|
177
|
+
return undefined;
|
|
178
|
+
}
|
|
179
|
+
return { owner: value.owner, reason: value.reason };
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
function isQuestionResolutionOwner(value: unknown): value is QuestionResolutionOwner {
|
|
183
|
+
return value === "agent" || value === "user" || value === "environment" || value === "unknown";
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
function isQuestionGate(value: unknown): value is QuestionGate {
|
|
187
|
+
return value === "none" || value === "before-direct" || value === "before-completion";
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
function isQuestionUpdateStatus(value: unknown): value is QuestionUpdateStatus {
|
|
191
|
+
return value === "resolved" || value === "not-applicable" || value === "open" || value === "blocked";
|
|
192
|
+
}
|
|
193
|
+
|
|
214
194
|
function parsePendingQuestion(value: unknown): PendingQuestion | undefined {
|
|
215
195
|
if (!isObject(value)) return undefined;
|
|
216
196
|
if (
|
|
217
197
|
typeof value.id !== "string" ||
|
|
218
198
|
typeof value.question !== "string" ||
|
|
219
|
-
(value.status !== "
|
|
199
|
+
(value.status !== "open" && value.status !== "blocked" && value.status !== "needs-evidence") ||
|
|
220
200
|
typeof value.sourceRouteId !== "string"
|
|
221
201
|
) {
|
|
222
202
|
return undefined;
|
|
223
203
|
}
|
|
204
|
+
const legacyBlocked = value.status === "blocked";
|
|
205
|
+
let resolutionOwner: QuestionResolutionOwner = legacyBlocked ? "unknown" : "agent";
|
|
206
|
+
if (isQuestionResolutionOwner(value.resolutionOwner)) resolutionOwner = value.resolutionOwner;
|
|
207
|
+
let gate: QuestionGate = legacyBlocked ? "before-completion" : "none";
|
|
208
|
+
if (isQuestionGate(value.gate)) gate = value.gate;
|
|
209
|
+
const resolutionCriteria =
|
|
210
|
+
typeof value.resolutionCriteria === "string"
|
|
211
|
+
? value.resolutionCriteria
|
|
212
|
+
: `Obtain evidence that settles: ${value.question}`;
|
|
224
213
|
return {
|
|
225
214
|
id: value.id,
|
|
226
215
|
question: value.question,
|
|
227
|
-
status:
|
|
216
|
+
status: legacyBlocked ? "blocked" : "open",
|
|
217
|
+
resolutionOwner,
|
|
218
|
+
gate,
|
|
219
|
+
resolutionCriteria,
|
|
228
220
|
sourceRouteId: value.sourceRouteId,
|
|
229
221
|
};
|
|
230
222
|
}
|
|
231
223
|
|
|
224
|
+
function parseQuestionUpdate(value: unknown): QuestionUpdate | undefined {
|
|
225
|
+
if (
|
|
226
|
+
!isObject(value) ||
|
|
227
|
+
typeof value.questionId !== "string" ||
|
|
228
|
+
!isQuestionUpdateStatus(value.status) ||
|
|
229
|
+
typeof value.result !== "string" ||
|
|
230
|
+
!isStringArray(value.basis)
|
|
231
|
+
) {
|
|
232
|
+
return undefined;
|
|
233
|
+
}
|
|
234
|
+
return {
|
|
235
|
+
questionId: value.questionId,
|
|
236
|
+
status: value.status,
|
|
237
|
+
result: value.result,
|
|
238
|
+
basis: value.basis,
|
|
239
|
+
};
|
|
240
|
+
}
|
|
241
|
+
|
|
232
242
|
export function normalizeDeveloperEvent(value: unknown): DeveloperEvent | undefined {
|
|
233
243
|
if (!isObject(value)) return undefined;
|
|
234
244
|
if (
|
|
235
245
|
value.protocol !== PROTOCOL &&
|
|
236
246
|
value.protocol !== PREVIOUS_PROTOCOL &&
|
|
247
|
+
value.protocol !== OLDER_PROTOCOL &&
|
|
237
248
|
value.protocol !== LEGACY_PROTOCOL
|
|
238
249
|
) return undefined;
|
|
239
250
|
|
|
@@ -243,7 +254,10 @@ export function normalizeDeveloperEvent(value: unknown): DeveloperEvent | undefi
|
|
|
243
254
|
}
|
|
244
255
|
|
|
245
256
|
if (value.kind === "focus") {
|
|
246
|
-
if (
|
|
257
|
+
if (
|
|
258
|
+
(value.protocol !== PROTOCOL && value.protocol !== PREVIOUS_PROTOCOL) ||
|
|
259
|
+
typeof value.questionId !== "string"
|
|
260
|
+
) return undefined;
|
|
247
261
|
return { protocol: PROTOCOL, kind: "focus", questionId: value.questionId };
|
|
248
262
|
}
|
|
249
263
|
|
|
@@ -254,6 +268,7 @@ export function normalizeDeveloperEvent(value: unknown): DeveloperEvent | undefi
|
|
|
254
268
|
typeof value.owner !== "string" ||
|
|
255
269
|
typeof value.reason !== "string" ||
|
|
256
270
|
!isStringArray(value.knownEvidence) ||
|
|
271
|
+
(value.consideredAlternatives !== undefined && !Array.isArray(value.consideredAlternatives)) ||
|
|
257
272
|
(value.targetQuestionId !== undefined && typeof value.targetQuestionId !== "string") ||
|
|
258
273
|
(value.methodLocation !== undefined && typeof value.methodLocation !== "string") ||
|
|
259
274
|
(value.executionProfile !== undefined && !isDirectExecutionProfile(value.executionProfile)) ||
|
|
@@ -261,6 +276,10 @@ export function normalizeDeveloperEvent(value: unknown): DeveloperEvent | undefi
|
|
|
261
276
|
) {
|
|
262
277
|
return undefined;
|
|
263
278
|
}
|
|
279
|
+
const consideredAlternatives = Array.isArray(value.consideredAlternatives)
|
|
280
|
+
? value.consideredAlternatives.map(parseRouteAlternative)
|
|
281
|
+
: [];
|
|
282
|
+
if (consideredAlternatives.some((alternative) => !alternative)) return undefined;
|
|
264
283
|
return {
|
|
265
284
|
protocol: PROTOCOL,
|
|
266
285
|
kind: "route",
|
|
@@ -269,6 +288,7 @@ export function normalizeDeveloperEvent(value: unknown): DeveloperEvent | undefi
|
|
|
269
288
|
owner: value.owner,
|
|
270
289
|
reason: value.reason,
|
|
271
290
|
knownEvidence: value.knownEvidence,
|
|
291
|
+
consideredAlternatives: consideredAlternatives as RouteAlternative[],
|
|
272
292
|
targetQuestionId: value.targetQuestionId,
|
|
273
293
|
methodLocation: value.methodLocation,
|
|
274
294
|
executionProfile: value.executionProfile,
|
|
@@ -303,9 +323,13 @@ export function normalizeDeveloperEvent(value: unknown): DeveloperEvent | undefi
|
|
|
303
323
|
openedQuestions: value.openQuestions.map((question, index) => ({
|
|
304
324
|
id: `question:${value.routeId}:legacy:${index + 1}`,
|
|
305
325
|
question,
|
|
306
|
-
status: "
|
|
326
|
+
status: "open",
|
|
327
|
+
resolutionOwner: "agent",
|
|
328
|
+
gate: "none",
|
|
329
|
+
resolutionCriteria: `Obtain evidence that settles: ${question}`,
|
|
307
330
|
sourceRouteId: String(value.routeId),
|
|
308
331
|
})),
|
|
332
|
+
questionUpdates: [],
|
|
309
333
|
artifacts: value.artifacts,
|
|
310
334
|
changedArtifacts: false,
|
|
311
335
|
};
|
|
@@ -314,6 +338,11 @@ export function normalizeDeveloperEvent(value: unknown): DeveloperEvent | undefi
|
|
|
314
338
|
if (!Array.isArray(value.openedQuestions)) return undefined;
|
|
315
339
|
const openedQuestions = value.openedQuestions.map(parsePendingQuestion);
|
|
316
340
|
if (openedQuestions.some((question) => !question)) return undefined;
|
|
341
|
+
if (value.questionUpdates !== undefined && !Array.isArray(value.questionUpdates)) return undefined;
|
|
342
|
+
const questionUpdates = Array.isArray(value.questionUpdates)
|
|
343
|
+
? value.questionUpdates.map(parseQuestionUpdate)
|
|
344
|
+
: [];
|
|
345
|
+
if (questionUpdates.some((update) => !update)) return undefined;
|
|
317
346
|
return {
|
|
318
347
|
protocol: PROTOCOL,
|
|
319
348
|
kind: "judgment",
|
|
@@ -324,6 +353,7 @@ export function normalizeDeveloperEvent(value: unknown): DeveloperEvent | undefi
|
|
|
324
353
|
result: value.result,
|
|
325
354
|
basis: value.basis,
|
|
326
355
|
openedQuestions: openedQuestions as PendingQuestion[],
|
|
356
|
+
questionUpdates: questionUpdates as QuestionUpdate[],
|
|
327
357
|
artifacts: value.artifacts,
|
|
328
358
|
changedArtifacts: typeof value.changedArtifacts === "boolean" ? value.changedArtifacts : false,
|
|
329
359
|
};
|
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
import type { DeveloperMode } from "./state.ts";
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
export type ControlledToolCapability = "execute" | "mutate";
|
|
4
|
+
|
|
5
|
+
const CONTROLLED_BUILTIN_CAPABILITIES = new Map<string, ControlledToolCapability>([
|
|
6
|
+
["bash", "execute"],
|
|
7
|
+
["edit", "mutate"],
|
|
8
|
+
["write", "mutate"],
|
|
9
|
+
]);
|
|
4
10
|
|
|
5
11
|
export interface ToolMetadataLike {
|
|
6
12
|
name: string;
|
|
@@ -16,46 +22,69 @@ export interface ToolPolicyResult {
|
|
|
16
22
|
memory: ToolPolicyMemory;
|
|
17
23
|
}
|
|
18
24
|
|
|
19
|
-
export
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
+
export interface ProtocolToolAccess {
|
|
26
|
+
canExecute: boolean;
|
|
27
|
+
canMutate: boolean;
|
|
28
|
+
hasBeforeDirectGate: boolean;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function builtinControlledToolCapabilities(
|
|
32
|
+
tools: ToolMetadataLike[],
|
|
33
|
+
): Map<string, ControlledToolCapability> {
|
|
34
|
+
const result = new Map<string, ControlledToolCapability>();
|
|
35
|
+
for (const tool of tools) {
|
|
36
|
+
if (tool.sourceInfo.source !== "builtin") continue;
|
|
37
|
+
const capability = CONTROLLED_BUILTIN_CAPABILITIES.get(tool.name);
|
|
38
|
+
if (capability) result.set(tool.name, capability);
|
|
39
|
+
}
|
|
40
|
+
return result;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function isControlledToolAllowed(input: {
|
|
44
|
+
mode: DeveloperMode;
|
|
45
|
+
capability: ControlledToolCapability;
|
|
46
|
+
access: ProtocolToolAccess;
|
|
47
|
+
}): boolean {
|
|
48
|
+
if (input.mode === "off") return true;
|
|
49
|
+
|
|
50
|
+
if (input.access.hasBeforeDirectGate) {
|
|
51
|
+
if (input.capability === "mutate") return false;
|
|
52
|
+
return input.access.canExecute && !input.access.canMutate;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (input.mode === "on") return true;
|
|
56
|
+
if (input.capability === "execute") return input.access.canExecute;
|
|
57
|
+
return input.access.canMutate;
|
|
25
58
|
}
|
|
26
59
|
|
|
27
60
|
export function reconcileProtocolTools(input: {
|
|
28
61
|
activeTools: string[];
|
|
29
62
|
allTools: ToolMetadataLike[];
|
|
30
63
|
mode: DeveloperMode;
|
|
31
|
-
|
|
64
|
+
access: ProtocolToolAccess;
|
|
32
65
|
protocolTools: readonly string[];
|
|
33
66
|
memory: ToolPolicyMemory;
|
|
34
67
|
}): ToolPolicyResult {
|
|
35
68
|
const active = new Set(input.activeTools);
|
|
36
|
-
const
|
|
69
|
+
const controlledBuiltins = builtinControlledToolCapabilities(input.allTools);
|
|
37
70
|
const withheld = new Set(
|
|
38
|
-
[...input.memory.withheldBuiltins].filter((name) =>
|
|
71
|
+
[...input.memory.withheldBuiltins].filter((name) => controlledBuiltins.has(name)),
|
|
39
72
|
);
|
|
40
73
|
|
|
41
|
-
if (input.mode === "
|
|
74
|
+
if (input.mode === "off") {
|
|
75
|
+
for (const name of withheld) active.add(name);
|
|
76
|
+
withheld.clear();
|
|
77
|
+
for (const tool of input.protocolTools) active.delete(tool);
|
|
78
|
+
} else {
|
|
42
79
|
for (const tool of input.protocolTools) active.add(tool);
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
80
|
+
for (const [name, capability] of controlledBuiltins) {
|
|
81
|
+
const allowed = isControlledToolAllowed({ mode: input.mode, capability, access: input.access });
|
|
82
|
+
if (allowed) {
|
|
83
|
+
if (withheld.delete(name)) active.add(name);
|
|
84
|
+
} else if (active.delete(name)) {
|
|
85
|
+
withheld.add(name);
|
|
49
86
|
}
|
|
50
87
|
}
|
|
51
|
-
} else {
|
|
52
|
-
for (const tool of withheld) active.add(tool);
|
|
53
|
-
withheld.clear();
|
|
54
|
-
|
|
55
|
-
for (const tool of input.protocolTools) {
|
|
56
|
-
if (input.mode === "on") active.add(tool);
|
|
57
|
-
else active.delete(tool);
|
|
58
|
-
}
|
|
59
88
|
}
|
|
60
89
|
|
|
61
90
|
return {
|