pi-feats 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.
Files changed (133) hide show
  1. package/README.md +65 -6
  2. package/THIRD_PARTY_NOTICES.md +12 -0
  3. package/extensions/api-server/application-log-store.ts +35 -2
  4. package/extensions/api-server/application-runtime.ts +95 -23
  5. package/extensions/api-server/application-store.ts +26 -9
  6. package/extensions/api-server/profile-store.ts +79 -29
  7. package/extensions/api-server/server.ts +225 -51
  8. package/extensions/browser-harness/cdp/commands.ts +260 -0
  9. package/extensions/browser-harness/cdp/console-buffer.ts +188 -0
  10. package/extensions/browser-harness/cdp/discovery.ts +198 -0
  11. package/extensions/browser-harness/cdp/errors.ts +23 -0
  12. package/extensions/browser-harness/cdp/events.ts +170 -0
  13. package/extensions/browser-harness/cdp/network-buffer.ts +146 -0
  14. package/extensions/browser-harness/cdp/ownership.ts +73 -0
  15. package/extensions/browser-harness/cdp/record-store.ts +61 -0
  16. package/extensions/browser-harness/cdp/session.ts +403 -0
  17. package/extensions/browser-harness/cdp/steel-transport.ts +151 -0
  18. package/extensions/browser-harness/cdp/target-factory.ts +153 -0
  19. package/extensions/browser-harness/cdp/types.ts +82 -0
  20. package/extensions/browser-harness/client.ts +366 -0
  21. package/extensions/browser-harness/daemon/bridge.ts +531 -0
  22. package/extensions/browser-harness/daemon/index.ts +86 -0
  23. package/extensions/browser-harness/daemon/protocol.ts +110 -0
  24. package/extensions/browser-harness/daemon/queue.ts +85 -0
  25. package/extensions/browser-harness/daemon/server.ts +170 -0
  26. package/extensions/browser-harness/daemon/spawn.ts +124 -0
  27. package/extensions/browser-harness/daemon/transport.ts +178 -0
  28. package/extensions/browser-harness/domains/ax-tree.ts +179 -0
  29. package/extensions/browser-harness/domains/box.ts +51 -0
  30. package/extensions/browser-harness/domains/cdp-call.ts +55 -0
  31. package/extensions/browser-harness/domains/click.ts +97 -0
  32. package/extensions/browser-harness/domains/console.ts +219 -0
  33. package/extensions/browser-harness/domains/dialog.ts +34 -0
  34. package/extensions/browser-harness/domains/drag.ts +67 -0
  35. package/extensions/browser-harness/domains/element-call.ts +117 -0
  36. package/extensions/browser-harness/domains/files.ts +219 -0
  37. package/extensions/browser-harness/domains/fill-engine.ts +80 -0
  38. package/extensions/browser-harness/domains/form-batch.ts +65 -0
  39. package/extensions/browser-harness/domains/form.ts +283 -0
  40. package/extensions/browser-harness/domains/headers.ts +89 -0
  41. package/extensions/browser-harness/domains/history.ts +80 -0
  42. package/extensions/browser-harness/domains/isolated-tab.ts +91 -0
  43. package/extensions/browser-harness/domains/js.ts +241 -0
  44. package/extensions/browser-harness/domains/keyboard.ts +197 -0
  45. package/extensions/browser-harness/domains/navigate.ts +161 -0
  46. package/extensions/browser-harness/domains/network.ts +301 -0
  47. package/extensions/browser-harness/domains/page.ts +154 -0
  48. package/extensions/browser-harness/domains/readpage/read-page.ts +174 -0
  49. package/extensions/browser-harness/domains/readpage/readability.ts +65 -0
  50. package/extensions/browser-harness/domains/ref-resolve.ts +118 -0
  51. package/extensions/browser-harness/domains/render.ts +43 -0
  52. package/extensions/browser-harness/domains/screenshot-capture.ts +80 -0
  53. package/extensions/browser-harness/domains/screenshot.ts +76 -0
  54. package/extensions/browser-harness/domains/scroll.ts +59 -0
  55. package/extensions/browser-harness/domains/search/google-serp.ts +85 -0
  56. package/extensions/browser-harness/domains/search/web-search.ts +144 -0
  57. package/extensions/browser-harness/domains/setup.ts +35 -0
  58. package/extensions/browser-harness/domains/snapshot.ts +256 -0
  59. package/extensions/browser-harness/domains/tabs.ts +202 -0
  60. package/extensions/browser-harness/domains/target.ts +35 -0
  61. package/extensions/browser-harness/domains/viewport.ts +42 -0
  62. package/extensions/browser-harness/index.ts +88 -0
  63. package/extensions/browser-harness/profile/bind.ts +90 -0
  64. package/extensions/browser-harness/profile/browser-process.ts +320 -0
  65. package/extensions/browser-harness/profile/command.ts +189 -0
  66. package/extensions/browser-harness/profile/launch.ts +80 -0
  67. package/extensions/browser-harness/profile/list.ts +131 -0
  68. package/extensions/browser-harness/profile/paths.ts +103 -0
  69. package/extensions/browser-harness/profile/store.ts +84 -0
  70. package/extensions/browser-harness/prompt.ts +16 -0
  71. package/extensions/browser-harness/registry.ts +80 -0
  72. package/extensions/browser-harness/schemas/common.ts +11 -0
  73. package/extensions/browser-harness/schemas/parse.ts +23 -0
  74. package/extensions/browser-harness/setup.ts +47 -0
  75. package/extensions/browser-harness/state.ts +51 -0
  76. package/extensions/browser-harness/steel.ts +215 -0
  77. package/extensions/browser-harness/util/debug.ts +8 -0
  78. package/extensions/browser-harness/util/guards.ts +30 -0
  79. package/extensions/browser-harness/util/js-template.ts +12 -0
  80. package/extensions/browser-harness/util/mutex.ts +20 -0
  81. package/extensions/browser-harness/util/paths.ts +31 -0
  82. package/extensions/browser-harness/util/result.ts +9 -0
  83. package/extensions/browser-harness/util/sharp-shim.ts +42 -0
  84. package/extensions/browser-harness/util/tool.ts +185 -0
  85. package/extensions/browser-harness/util/truncate.ts +41 -0
  86. package/extensions/cli-resources.ts +54 -35
  87. package/extensions/composio/index.ts +163 -0
  88. package/extensions/context-memory/index.ts +58 -0
  89. package/extensions/conversation-search/index.ts +42 -0
  90. package/extensions/lib/application-context.ts +22 -0
  91. package/extensions/lib/application-handler-templates.ts +17 -1
  92. package/extensions/lib/builtin-tools.ts +24 -0
  93. package/extensions/lib/context-memory.ts +165 -0
  94. package/extensions/lib/conversation-search.ts +33 -0
  95. package/extensions/lib/profile-sandbox.ts +6 -0
  96. package/extensions/lib/workflow-harness.ts +238 -0
  97. package/extensions/pi-console-webui/app/[section]/page.tsx +6 -1
  98. package/extensions/pi-console-webui/app/api/admin/models/route.ts +22 -0
  99. package/extensions/pi-console-webui/app/applications/[slug]/page.tsx +15 -0
  100. package/extensions/pi-console-webui/app/layout.tsx +6 -1
  101. package/extensions/pi-console-webui/app/login/page.tsx +5 -9
  102. package/extensions/pi-console-webui/app/not-found.tsx +5 -0
  103. package/extensions/pi-console-webui/app/page.tsx +6 -0
  104. package/extensions/pi-console-webui/app/terminal/page.tsx +3 -0
  105. package/extensions/pi-console-webui/components/application-handler-editor.tsx +4 -4
  106. package/extensions/pi-console-webui/components/application-logs.tsx +14 -10
  107. package/extensions/pi-console-webui/components/application-mappings.tsx +10 -12
  108. package/extensions/pi-console-webui/components/application-settings.tsx +5 -1
  109. package/extensions/pi-console-webui/components/chat-workspace.tsx +7 -4
  110. package/extensions/pi-console-webui/components/console-page.tsx +6 -1
  111. package/extensions/pi-console-webui/components/console.tsx +60 -10
  112. package/extensions/pi-console-webui/components/login-form.tsx +14 -0
  113. package/extensions/pi-console-webui/components/models-config-form.tsx +31 -0
  114. package/extensions/pi-console-webui/components/profile-model-form.tsx +54 -0
  115. package/extensions/pi-console-webui/components/pulse-resources.tsx +14 -10
  116. package/extensions/pi-console-webui/components/skill-resources.tsx +3 -3
  117. package/extensions/pi-console-webui/lib/admin-models.ts +36 -0
  118. package/extensions/pi-console-webui/lib/page-metadata.ts +35 -0
  119. package/extensions/pi-console-webui/next.config.js +1 -0
  120. package/extensions/profile-ssh.ts +274 -0
  121. package/extensions/profiles.ts +70 -23
  122. package/extensions/pulse/index.ts +128 -14
  123. package/extensions/pulse/store.ts +110 -15
  124. package/extensions/sequential-workflow.ts +340 -256
  125. package/extensions/skill-sources/store.ts +24 -3
  126. package/licenses/pi-browser-harness-MIT.txt +21 -0
  127. package/package.json +23 -3
  128. package/schemas/sequential-workflow-template.schema.json +62 -0
  129. package/skills/browser-harness/SKILL.md +33 -0
  130. package/skills/composio-connections/SKILL.md +75 -0
  131. package/skills/profile-ssh/SKILL.md +65 -0
  132. package/skills/sequential-workflow/SKILL.md +70 -0
  133. package/skills/sequential-workflow-template/SKILL.md +44 -0
@@ -1,14 +1,16 @@
1
1
  import { existsSync } from "node:fs";
2
- import { copyFile, mkdir, readFile, readdir, rm, writeFile } from "node:fs/promises";
2
+ import { copyFile, mkdir, readFile, readlink, readdir, rm, symlink, writeFile } from "node:fs/promises";
3
3
  import { join, relative, resolve } from "node:path";
4
4
  import { ensureProfileSandbox } from "../lib/profile-sandbox.ts";
5
+ import { BUILTIN_TOOLS, activeBuiltinTools, updatedBuiltinTools } from "../lib/builtin-tools.ts";
5
6
 
6
- export const BUILTIN_TOOLS = ["read", "bash", "powershell", "edit", "write", "grep", "find", "ls"];
7
+ export { BUILTIN_TOOLS } from "../lib/builtin-tools.ts";
7
8
  export const RESOURCE_KINDS = ["tools", "skills", "extensions"] as const;
8
9
  export type ResourceKind = typeof RESOURCE_KINDS[number];
9
10
  type PolicyKey = "enabledTools" | "enabledSkills" | "enabledProfileSkills" | "enabledExtensions";
10
11
  type SkillSources = { shared?: boolean; profile?: boolean };
11
- type ProfilePolicy = Partial<Record<PolicyKey, string[]>> & { skillSources?: SkillSources };
12
+ type SkillResourceSource = "shared" | "profile" | "package";
13
+ type ProfilePolicy = Partial<Record<PolicyKey, string[]>> & { skillSources?: SkillSources; contextMemory?: { mode: "file"; target: "profile" | "identity" } | { mode: "handler"; handler: string } };
12
14
  export type ProfileSettings = Record<string, unknown> & { profile?: ProfilePolicy; sandbox?: boolean };
13
15
  export type Resource = { name: string; kind: ResourceKind; path?: string; source: "builtin" | "shared" | "profile" | "extension" | "package"; package?: string; enabled: boolean; protected?: boolean };
14
16
  export type Package = { name: string; version?: string; description?: string; enabled: boolean; installed: boolean };
@@ -22,12 +24,24 @@ const policyKey: Record<ResourceKind, PolicyKey> = {
22
24
  const protectedExtensions = new Set(["profiles", "api-server"]);
23
25
 
24
26
  export class ProfileStore {
25
- private readonly extensionTools = new Set<string>();
27
+ private readonly extensionTools = new Map<string, Set<string>>();
28
+ readonly agentDir: string;
26
29
 
27
- constructor(readonly agentDir: string) {}
30
+ constructor(agentDir: string) { this.agentDir = agentDir; }
28
31
 
29
- registerExtensionTools(names: Iterable<string>): void {
30
- for (const name of names) if (name && !BUILTIN_TOOLS.includes(name)) this.extensionTools.add(name);
32
+ registerExtensionTools(profile: string, names: Iterable<string>): void {
33
+ const tools = this.extensionTools.get(profile) ?? new Set<string>();
34
+ for (const name of names) if (name && !(BUILTIN_TOOLS as readonly string[]).includes(name)) tools.add(name);
35
+ this.extensionTools.set(profile, tools);
36
+ }
37
+
38
+ extensionToolNames(profile: string): Iterable<string> {
39
+ return this.extensionTools.get(profile) ?? [];
40
+ }
41
+
42
+ clearExtensionTools(profile?: string): void {
43
+ if (profile) this.extensionTools.delete(profile);
44
+ else this.extensionTools.clear();
31
45
  }
32
46
 
33
47
  private profilesDir(): string { return join(this.agentDir, "profiles"); }
@@ -37,6 +51,13 @@ export class ProfileStore {
37
51
  return join(this.profilesDir(), name);
38
52
  }
39
53
  private settingsPath(name: string): string { return join(this.directory(name), "settings.json"); }
54
+ private async linkSharedModels(name: string): Promise<void> {
55
+ if (name === "default") return;
56
+ const source = join(this.agentDir, "models.json"), target = join(this.directory(name), "models.json");
57
+ try { if (resolve(this.directory(name), await readlink(target)) === source) return; } catch { /* Missing or non-symlink target must be replaced. */ }
58
+ await rm(target, { force: true });
59
+ if (existsSync(source)) await symlink(source, target);
60
+ }
40
61
  private skillSources(name: string, settings: ProfileSettings): Required<SkillSources> {
41
62
  if (name === "default") return { shared: true, profile: false };
42
63
  return { shared: settings.profile?.skillSources?.shared !== false, profile: settings.profile?.skillSources?.profile === true };
@@ -50,6 +71,7 @@ export class ProfileStore {
50
71
  try {
51
72
  const value = JSON.parse(await readFile(path, "utf8")) as unknown;
52
73
  if (!value || typeof value !== "object" || Array.isArray(value)) throw new Error("settings.json must contain an object");
74
+ await this.linkSharedModels(name);
53
75
  return value as ProfileSettings;
54
76
  } catch (error) {
55
77
  if ((error as NodeJS.ErrnoException).code === "ENOENT" && name === "default") return {};
@@ -94,19 +116,32 @@ export class ProfileStore {
94
116
  private enabled(settings: ProfileSettings, kind: ResourceKind, name: string, path?: string): boolean {
95
117
  const values = settings.profile?.[policyKey[kind]];
96
118
  if (values) return values.includes("*") || values.includes(name);
119
+ if (kind === "tools" && (BUILTIN_TOOLS as readonly string[]).includes(name)) return activeBuiltinTools(settings).includes(name);
97
120
  const configured = settings[kind];
98
121
  const exclusions = Array.isArray(configured) ? configured.filter((value): value is string => typeof value === "string" && value.startsWith("!")) : [];
99
122
  return !path || !exclusions.includes(`!${path}`);
100
123
  }
101
124
 
102
- private async skillResources(profile: string, settings: ProfileSettings): Promise<Resource[]> {
125
+ private async packageSkillResources(settings: ProfileSettings): Promise<Resource[]> {
126
+ const resources: Resource[] = [];
127
+ for (const pkg of await this.configuredPackages(settings)) {
128
+ for (const entry of Array.isArray(pkg.manifest?.pi?.skills) ? pkg.manifest.pi.skills : []) {
129
+ if (typeof entry !== "string") continue;
130
+ for (const skill of await this.skillNames(join(pkg.base, entry))) resources.push({ name: skill.name, kind: "skills", path: skill.path, source: "package", package: pkg.name, enabled: this.skillEnabled(settings, "package", skill.name) });
131
+ }
132
+ }
133
+ return resources;
134
+ }
135
+
136
+ private async skillResources(profile: string, settings: ProfileSettings, runtimeSettings: ProfileSettings = settings): Promise<Resource[]> {
103
137
  const sources = this.skillSources(profile, settings);
104
138
  const entries = await Promise.all((["profile", "shared"] as const).filter((source) => sources[source]).map(async (source) => ({ source, entries: await this.skillNames(this.skillBase(profile, source)) })));
105
- return entries.flatMap(({ source, entries }) => entries.map(({ name, path }) => ({ name, kind: "skills" as const, path, source, enabled: this.skillEnabled(settings, source, name) })));
139
+ const local = entries.flatMap(({ source, entries }) => entries.map(({ name, path }) => ({ name, kind: "skills" as const, path, source, enabled: this.skillEnabled(settings, source, name) })));
140
+ return [...local, ...(sources.shared ? await this.packageSkillResources(runtimeSettings) : [])];
106
141
  }
107
142
 
108
- private skillEnabled(settings: ProfileSettings, source: "shared" | "profile", name: string): boolean {
109
- const values = settings.profile?.[source === "shared" ? "enabledSkills" : "enabledProfileSkills"];
143
+ private skillEnabled(settings: ProfileSettings, source: SkillResourceSource, name: string): boolean {
144
+ const values = settings.profile?.[source === "profile" ? "enabledProfileSkills" : "enabledSkills"];
110
145
  return !values || values.includes("*") || values.includes(name);
111
146
  }
112
147
 
@@ -120,12 +155,12 @@ export class ProfileStore {
120
155
  return join(this.agentDir, "git", remote);
121
156
  }
122
157
 
123
- private async configuredPackages(settings: ProfileSettings): Promise<Array<{ source: string; base: string; name: string; manifest?: { version?: string; description?: string; pi?: { extensions?: unknown } } }>> {
158
+ private async configuredPackages(settings: ProfileSettings): Promise<Array<{ source: string; base: string; name: string; manifest?: { version?: string; description?: string; pi?: { extensions?: unknown; skills?: unknown } } }>> {
124
159
  const sources = Array.isArray(settings.packages) ? settings.packages.filter((value): value is string => typeof value === "string") : [];
125
- const packages: Array<{ source: string; base: string; name: string; manifest?: { version?: string; description?: string; pi?: { extensions?: unknown } } }> = [];
160
+ const packages: Array<{ source: string; base: string; name: string; manifest?: { version?: string; description?: string; pi?: { extensions?: unknown; skills?: unknown } } }> = [];
126
161
  for (const source of sources) {
127
162
  const base = this.packageBase(source); if (!base) continue;
128
- try { const manifest = JSON.parse(await readFile(join(base, "package.json"), "utf8")) as { name?: string; version?: string; description?: string; pi?: { extensions?: unknown } }; packages.push({ source, base, name: manifest.name ?? source, manifest }); }
163
+ try { const manifest = JSON.parse(await readFile(join(base, "package.json"), "utf8")) as { name?: string; version?: string; description?: string; pi?: { extensions?: unknown; skills?: unknown } }; packages.push({ source, base, name: manifest.name ?? source, manifest }); }
129
164
  catch { packages.push({ source, base, name: source.replace(/^npm:/, "") }); }
130
165
  }
131
166
  return packages;
@@ -159,8 +194,8 @@ export class ProfileStore {
159
194
  const sources = new Map<string, string>(), tools = [...names];
160
195
  for (const extension of await this.packageExtensions(settings)) try {
161
196
  const source = await readFile(extension.path, "utf8");
162
- for (const tool of tools) if (source.includes(`"${tool}"`) || source.includes(`'${tool}'`) || source.includes(`\`${tool}\``)) sources.set(tool, extension.package);
163
- for (const block of source.matchAll(/TOOL_NAMES[^=]*=\s*\{([\s\S]*?)\}/g)) for (const name of block[1].matchAll(/:\s*["']([A-Za-z][A-Za-z0-9_-]{0,63})["']/g)) sources.set(name[1], extension.package);
197
+ for (const tool of tools) if (!(BUILTIN_TOOLS as readonly string[]).includes(tool) && (source.includes(`"${tool}"`) || source.includes(`'${tool}'`) || source.includes(`\`${tool}\``))) sources.set(tool, extension.package);
198
+ for (const block of source.matchAll(/TOOL_NAMES[^=]*=\s*\{([\s\S]*?)\}/g)) for (const name of block[1].matchAll(/:\s*["']([A-Za-z][A-Za-z0-9_-]{0,63})["']/g)) if (!(BUILTIN_TOOLS as readonly string[]).includes(name[1])) sources.set(name[1], extension.package);
164
199
  } catch {}
165
200
  return sources;
166
201
  }
@@ -168,23 +203,25 @@ export class ProfileStore {
168
203
  async resources(profile: string, kind: ResourceKind): Promise<Resource[]> {
169
204
  const settings = await this.readSettings(profile);
170
205
  const runtimeSettings = profile === "default" ? settings : await this.readSettings("default");
171
- if (kind === "skills") return this.skillResources(profile, settings);
206
+ if (kind === "skills") return this.skillResources(profile, settings, runtimeSettings);
172
207
  if (kind === "tools") {
173
- const sources = await this.packageToolSources(runtimeSettings, this.extensionTools);
174
- const names = new Set([...this.extensionTools, ...sources.keys()]);
208
+ const extensionTools = this.extensionToolNames(profile);
209
+ const sources = await this.packageToolSources(runtimeSettings, extensionTools);
210
+ const names = new Set([...extensionTools, ...sources.keys()]);
175
211
  return [...BUILTIN_TOOLS.map((name) => ({ name, kind, source: "builtin" as const, enabled: this.enabled(settings, kind, name) })), ...[...names].sort().map((name) => sources.has(name) ? ({ name, kind, source: "package" as const, package: sources.get(name), enabled: this.enabled(settings, kind, name) }) : ({ name, kind, source: "extension" as const, enabled: this.enabled(settings, kind, name) }))];
176
212
  }
177
- return [...(await this.names("extensions")).map(({ name, path }) => ({ name, kind, path, source: "shared" as const, enabled: protectedExtensions.has(name) ? true : this.enabled(runtimeSettings, kind, name, path), protected: protectedExtensions.has(name) || undefined })), ...(await this.packageExtensions(runtimeSettings)).map(({ name, path, package: packageName }) => ({ name, kind, path, source: "package" as const, package: packageName, enabled: true }))];
213
+ return [...(await this.names("extensions")).map(({ name, path }) => ({ name, kind, path, source: "shared" as const, enabled: protectedExtensions.has(name) ? true : this.enabled(runtimeSettings, kind, name, path), protected: protectedExtensions.has(name) || undefined })), ...(await this.packageExtensions(runtimeSettings)).map(({ name, path, package: packageName }) => ({ name, kind, path, source: "package" as const, package: packageName, enabled: protectedExtensions.has(name) ? true : this.enabled(runtimeSettings, kind, name, path), protected: protectedExtensions.has(name) || undefined }))];
178
214
  }
179
215
 
180
- async resource(profile: string, kind: ResourceKind, name: string, source?: "shared" | "profile"): Promise<Resource> {
216
+ async resource(profile: string, kind: ResourceKind, name: string, source?: SkillResourceSource): Promise<Resource> {
181
217
  const resource = (await this.resources(profile, kind)).find((item) => item.name === name && (!source || item.source === source));
182
218
  if (!resource) throw Object.assign(new Error("Resource not found"), { status: 404 });
183
219
  return resource;
184
220
  }
185
221
 
186
222
  private async skillPaths(name: string, settings: ProfileSettings): Promise<string[]> {
187
- return (await this.skillResources(name, settings)).filter((skill) => skill.enabled).map((skill) => skill.path!).filter(Boolean);
223
+ const runtimeSettings = name === "default" ? settings : await this.readSettings("default");
224
+ return (await this.skillResources(name, settings, runtimeSettings)).filter((skill) => skill.enabled).map((skill) => skill.path!).filter(Boolean);
188
225
  }
189
226
 
190
227
  private async materializeResources(name: string, settings: ProfileSettings): Promise<ProfileSettings> {
@@ -232,7 +269,7 @@ export class ProfileStore {
232
269
  return this.skillSources(profile, settings);
233
270
  }
234
271
 
235
- async readSkillDocument(profile: string, name: string, source?: "shared" | "profile"): Promise<{ name: string; frontmatter: Record<string, string>; content: string }> {
272
+ async readSkillDocument(profile: string, name: string, source?: SkillResourceSource): Promise<{ name: string; frontmatter: Record<string, string>; content: string }> {
236
273
  const skill = await this.resource(profile, "skills", name, source);
237
274
  if (!skill.path) throw Object.assign(new Error("Skill document not found"), { status: 404 });
238
275
  const document = await readFile(join(skill.path, "SKILL.md"), "utf8");
@@ -247,9 +284,22 @@ export class ProfileStore {
247
284
  return { name, frontmatter, content: match ? document.slice(match[0].length) : document };
248
285
  }
249
286
 
250
- async setResource(profile: string, kind: ResourceKind, name: string, enabled: boolean, source?: "shared" | "profile"): Promise<Resource> {
287
+ async setResource(profile: string, kind: ResourceKind, name: string, enabled: boolean, source?: SkillResourceSource): Promise<Resource> {
251
288
  const current = await this.resource(profile, kind, name, source);
252
289
  if (current.protected && !enabled) throw Object.assign(new Error("This extension is required and cannot be disabled."), { status: 422 });
290
+ // The default profile uses Pi's defaultTools setting for native tool state
291
+ // unless it already has a legacy explicit enabledTools policy. Named profiles
292
+ // retain their explicit enabledTools policy.
293
+ if (kind === "tools" && current.source === "builtin" && profile === "default") {
294
+ const settings = await this.readSettings(profile);
295
+ if (!settings.profile?.enabledTools) {
296
+ const tools = updatedBuiltinTools(settings, name, enabled);
297
+ if (tools) settings.defaultTools = tools;
298
+ else delete settings.defaultTools;
299
+ await this.writeSettings(profile, settings);
300
+ return this.resource(profile, kind, name, source);
301
+ }
302
+ }
253
303
  // Extensions are shared runtime resources, so toggling one always updates
254
304
  // the default runtime rather than materializing it in a profile workspace.
255
305
  const settingsProfile = kind === "extensions" ? "default" : profile;
@@ -264,8 +314,9 @@ export class ProfileStore {
264
314
  return this.resource(profile, kind, name, source);
265
315
  }
266
316
 
267
- async deleteSkill(profile: string, name: string, source?: "shared" | "profile"): Promise<void> {
317
+ async deleteSkill(profile: string, name: string, source?: SkillResourceSource): Promise<void> {
268
318
  const current = await this.resource(profile, "skills", name, source);
319
+ if (current.source === "package") throw Object.assign(new Error("Package Skills cannot be deleted here."), { status: 403 });
269
320
  if (current.source === "shared" && profile !== "default") { await this.setResource(profile, "skills", name, false, "shared"); return; }
270
321
  if (!current.path) throw Object.assign(new Error("Skill path not found."), { status: 404 });
271
322
  await rm(current.path, { recursive: true, force: false });
@@ -290,10 +341,9 @@ export class ProfileStore {
290
341
  await ensureProfileSandbox(destination, resolve(process.argv[1]));
291
342
  await writeFile(join(destination, "SOUL.md"), "", "utf8");
292
343
  await writeFile(join(destination, "guardrails.json"), "{\n \"guardrails\": []\n}\n", "utf8");
293
- for (const file of ["auth.json", "models.json"]) {
294
- const source = join(this.agentDir, file);
295
- if (existsSync(source)) await copyFile(source, join(destination, file));
296
- }
344
+ const auth = join(this.agentDir, "auth.json");
345
+ if (existsSync(auth)) await copyFile(auth, join(destination, "auth.json"));
346
+ await this.linkSharedModels(name);
297
347
  return { name, path: destination };
298
348
  }
299
349