@hobin/developer 0.1.6 → 0.1.8

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.
@@ -1,94 +1,187 @@
1
- import type { DeveloperMode } from "./state.ts";
1
+ export type ControlledToolCapability = "shell" | "artifact";
2
2
 
3
- export type ControlledToolCapability = "execute" | "mutate";
3
+ export const TOOL_POLICY_LIFECYCLE_ENTRY = "developer.tool-policy-lifecycle";
4
+ export const TOOL_POLICY_LIFECYCLE = "released-before-reload/v1";
4
5
 
5
- const CONTROLLED_BUILTIN_CAPABILITIES = new Map<string, ControlledToolCapability>([
6
- ["bash", "execute"],
7
- ["edit", "mutate"],
8
- ["write", "mutate"],
6
+ const CONTROLLED_BUILTIN_CAPABILITIES = new Map<
7
+ string,
8
+ ControlledToolCapability
9
+ >([
10
+ ["bash", "shell"],
11
+ ["edit", "artifact"],
12
+ ["write", "artifact"],
9
13
  ]);
10
14
 
11
15
  export interface ToolMetadataLike {
12
- name: string;
13
- sourceInfo: { source: string };
16
+ name: string;
17
+ sourceInfo: { source: string };
14
18
  }
15
19
 
16
20
  export interface ToolPolicyMemory {
17
- withheldBuiltins: Set<string>;
21
+ withheldBuiltins: Set<string>;
18
22
  }
19
23
 
20
24
  export interface ToolPolicyResult {
21
- activeTools: string[];
22
- memory: ToolPolicyMemory;
25
+ activeTools: string[];
26
+ memory: ToolPolicyMemory;
23
27
  }
24
28
 
25
29
  export interface ProtocolToolAccess {
26
- canExecute: boolean;
27
- canMutate: boolean;
28
- hasBeforeDirectGate: boolean;
30
+ allowsShell: boolean;
31
+ allowsArtifactTools: boolean;
32
+ hasBeforeImplementationGate: boolean;
33
+ }
34
+
35
+ interface ToolPolicyBranchEntry {
36
+ type?: string;
37
+ customType?: string;
38
+ data?: unknown;
39
+ message?: {
40
+ role?: string;
41
+ toolName?: string;
42
+ details?: unknown;
43
+ };
44
+ }
45
+
46
+ interface ToolPolicyLifecycleMarker {
47
+ protocol: string;
48
+ kind: "tool-policy-lifecycle";
49
+ lifecycle: typeof TOOL_POLICY_LIFECYCLE;
50
+ }
51
+
52
+ function isObject(value: unknown): value is Record<string, unknown> {
53
+ return typeof value === "object" && value !== null;
54
+ }
55
+
56
+ export function reloadSafeToolPolicyMarker(
57
+ protocol: string,
58
+ ): ToolPolicyLifecycleMarker {
59
+ return {
60
+ protocol,
61
+ kind: "tool-policy-lifecycle",
62
+ lifecycle: TOOL_POLICY_LIFECYCLE,
63
+ };
64
+ }
65
+
66
+ function isReloadSafeToolPolicyMarker(
67
+ entry: ToolPolicyBranchEntry,
68
+ protocol: string,
69
+ ): boolean {
70
+ if (
71
+ entry.type !== "custom" ||
72
+ entry.customType !== TOOL_POLICY_LIFECYCLE_ENTRY ||
73
+ !isObject(entry.data)
74
+ )
75
+ return false;
76
+ return (
77
+ entry.data.protocol === protocol &&
78
+ entry.data.kind === "tool-policy-lifecycle" &&
79
+ entry.data.lifecycle === TOOL_POLICY_LIFECYCLE
80
+ );
81
+ }
82
+
83
+ export function toolPolicyReloadRequiresRestart(input: {
84
+ entries: readonly ToolPolicyBranchEntry[];
85
+ protocol: string;
86
+ protocolTools: readonly string[];
87
+ }): boolean {
88
+ const protocolTools = new Set(input.protocolTools);
89
+ for (let index = input.entries.length - 1; index >= 0; index -= 1) {
90
+ const entry = input.entries[index];
91
+ if (!entry) continue;
92
+ if (isReloadSafeToolPolicyMarker(entry, input.protocol)) return false;
93
+
94
+ let value: unknown;
95
+ if (
96
+ entry.type === "custom" &&
97
+ entry.customType?.startsWith("developer.") &&
98
+ entry.customType !== TOOL_POLICY_LIFECYCLE_ENTRY
99
+ ) {
100
+ value = entry.data;
101
+ } else if (
102
+ entry.type === "message" &&
103
+ entry.message?.role === "toolResult" &&
104
+ entry.message.toolName &&
105
+ protocolTools.has(entry.message.toolName)
106
+ ) {
107
+ value = entry.message.details;
108
+ }
109
+ if (
110
+ isObject(value) &&
111
+ typeof value.protocol === "string" &&
112
+ value.protocol.startsWith("developer/")
113
+ )
114
+ return true;
115
+ }
116
+ return false;
29
117
  }
30
118
 
31
119
  export function builtinControlledToolCapabilities(
32
- tools: ToolMetadataLike[],
120
+ tools: ToolMetadataLike[],
33
121
  ): 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;
122
+ const result = new Map<string, ControlledToolCapability>();
123
+ for (const tool of tools) {
124
+ if (tool.sourceInfo.source !== "builtin") continue;
125
+ const capability = CONTROLLED_BUILTIN_CAPABILITIES.get(tool.name);
126
+ if (capability) result.set(tool.name, capability);
127
+ }
128
+ return result;
41
129
  }
42
130
 
43
131
  export function isControlledToolAllowed(input: {
44
- mode: DeveloperMode;
45
- capability: ControlledToolCapability;
46
- access: ProtocolToolAccess;
132
+ enabled: boolean;
133
+ capability: ControlledToolCapability;
134
+ access: ProtocolToolAccess;
47
135
  }): boolean {
48
- if (input.mode === "off") return true;
136
+ if (!input.enabled) return true;
49
137
 
50
- if (input.access.hasBeforeDirectGate) {
51
- if (input.capability === "mutate") return false;
52
- return input.access.canExecute && !input.access.canMutate;
53
- }
138
+ if (input.access.hasBeforeImplementationGate) {
139
+ if (input.capability === "artifact") return false;
140
+ return input.access.allowsShell && !input.access.allowsArtifactTools;
141
+ }
54
142
 
55
- if (input.mode === "on") return true;
56
- if (input.capability === "execute") return input.access.canExecute;
57
- return input.access.canMutate;
143
+ if (input.capability === "shell") return input.access.allowsShell;
144
+ return input.access.allowsArtifactTools;
58
145
  }
59
146
 
60
147
  export function reconcileProtocolTools(input: {
61
- activeTools: string[];
62
- allTools: ToolMetadataLike[];
63
- mode: DeveloperMode;
64
- access: ProtocolToolAccess;
65
- protocolTools: readonly string[];
66
- memory: ToolPolicyMemory;
148
+ activeTools: string[];
149
+ allTools: ToolMetadataLike[];
150
+ enabled: boolean;
151
+ access: ProtocolToolAccess;
152
+ protocolTools: readonly string[];
153
+ memory: ToolPolicyMemory;
67
154
  }): ToolPolicyResult {
68
- const active = new Set(input.activeTools);
69
- const controlledBuiltins = builtinControlledToolCapabilities(input.allTools);
70
- const withheld = new Set(
71
- [...input.memory.withheldBuiltins].filter((name) => controlledBuiltins.has(name)),
72
- );
73
-
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 {
79
- for (const tool of input.protocolTools) active.add(tool);
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);
86
- }
87
- }
88
- }
89
-
90
- return {
91
- activeTools: [...active],
92
- memory: { withheldBuiltins: withheld },
93
- };
155
+ const active = new Set(input.activeTools);
156
+ const controlledBuiltins = builtinControlledToolCapabilities(input.allTools);
157
+ const withheld = new Set(
158
+ [...input.memory.withheldBuiltins].filter((name) =>
159
+ controlledBuiltins.has(name),
160
+ ),
161
+ );
162
+
163
+ if (!input.enabled) {
164
+ for (const name of withheld) active.add(name);
165
+ withheld.clear();
166
+ for (const tool of input.protocolTools) active.delete(tool);
167
+ } else {
168
+ for (const tool of input.protocolTools) active.add(tool);
169
+ for (const [name, capability] of controlledBuiltins) {
170
+ const allowed = isControlledToolAllowed({
171
+ enabled: input.enabled,
172
+ capability,
173
+ access: input.access,
174
+ });
175
+ if (allowed) {
176
+ if (withheld.delete(name)) active.add(name);
177
+ } else if (active.delete(name)) {
178
+ withheld.add(name);
179
+ }
180
+ }
181
+ }
182
+
183
+ return {
184
+ activeTools: [...active],
185
+ memory: { withheldBuiltins: withheld },
186
+ };
94
187
  }