@anhlp/alp-core 0.0.0-beta.1

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 (144) hide show
  1. package/LICENSE +202 -0
  2. package/NOTICE +7 -0
  3. package/README.md +7 -0
  4. package/dist/adapter.d.ts +20 -0
  5. package/dist/adapter.js +30 -0
  6. package/dist/atomic.d.ts +2 -0
  7. package/dist/atomic.js +28 -0
  8. package/dist/backup.d.ts +27 -0
  9. package/dist/backup.js +582 -0
  10. package/dist/beads.d.ts +65 -0
  11. package/dist/beads.js +213 -0
  12. package/dist/delegation.d.ts +2 -0
  13. package/dist/delegation.js +32 -0
  14. package/dist/errors.d.ts +4 -0
  15. package/dist/errors.js +11 -0
  16. package/dist/formulas.d.ts +25 -0
  17. package/dist/formulas.js +201 -0
  18. package/dist/hook-run.d.ts +44 -0
  19. package/dist/hook-run.js +60 -0
  20. package/dist/hooks.d.ts +35 -0
  21. package/dist/hooks.js +30 -0
  22. package/dist/index.d.ts +30 -0
  23. package/dist/index.js +63 -0
  24. package/dist/init.d.ts +30 -0
  25. package/dist/init.js +88 -0
  26. package/dist/library-edit.d.ts +90 -0
  27. package/dist/library-edit.js +315 -0
  28. package/dist/library.d.ts +28 -0
  29. package/dist/library.js +108 -0
  30. package/dist/log.d.ts +37 -0
  31. package/dist/log.js +156 -0
  32. package/dist/migrations.d.ts +9 -0
  33. package/dist/migrations.js +95 -0
  34. package/dist/models.d.ts +11 -0
  35. package/dist/models.js +44 -0
  36. package/dist/permissions.d.ts +14 -0
  37. package/dist/permissions.js +229 -0
  38. package/dist/profiles.d.ts +27 -0
  39. package/dist/profiles.js +122 -0
  40. package/dist/promptsafe.d.ts +1 -0
  41. package/dist/promptsafe.js +13 -0
  42. package/dist/providers.d.ts +90 -0
  43. package/dist/providers.js +101 -0
  44. package/dist/resolver.d.ts +41 -0
  45. package/dist/resolver.js +240 -0
  46. package/dist/safe-path.d.ts +2 -0
  47. package/dist/safe-path.js +24 -0
  48. package/dist/shell.d.ts +16 -0
  49. package/dist/shell.js +22 -0
  50. package/dist/state-schema.d.ts +9 -0
  51. package/dist/state-schema.js +54 -0
  52. package/dist/task-rows.d.ts +47 -0
  53. package/dist/task-rows.js +45 -0
  54. package/dist/tasks.d.ts +128 -0
  55. package/dist/tasks.js +835 -0
  56. package/dist/teams.d.ts +69 -0
  57. package/dist/teams.js +159 -0
  58. package/dist/template-path.d.ts +2 -0
  59. package/dist/template-path.js +10 -0
  60. package/dist/trust.d.ts +8 -0
  61. package/dist/trust.js +47 -0
  62. package/dist/types.d.ts +51 -0
  63. package/dist/upgrade.d.ts +10 -0
  64. package/dist/upgrade.js +200 -0
  65. package/dist/user-settings.d.ts +6 -0
  66. package/dist/user-settings.js +72 -0
  67. package/dist/validation.d.ts +17 -0
  68. package/dist/validation.js +295 -0
  69. package/dist/verify.d.ts +15 -0
  70. package/dist/verify.js +123 -0
  71. package/dist/workflow.d.ts +14 -0
  72. package/dist/workflow.js +24 -0
  73. package/package.json +309 -0
  74. package/src/adapter.d.ts +20 -0
  75. package/src/adapter.js +29 -0
  76. package/src/atomic.d.ts +2 -0
  77. package/src/atomic.js +28 -0
  78. package/src/backup.d.ts +27 -0
  79. package/src/backup.js +570 -0
  80. package/src/beads.js +236 -0
  81. package/src/delegation.js +28 -0
  82. package/src/errors.js +7 -0
  83. package/src/formulas.d.ts +25 -0
  84. package/src/formulas.js +202 -0
  85. package/src/hook-run.js +57 -0
  86. package/src/hooks.js +36 -0
  87. package/src/index.js +31 -0
  88. package/src/init.js +92 -0
  89. package/src/library-edit.js +346 -0
  90. package/src/library.js +117 -0
  91. package/src/log.d.ts +37 -0
  92. package/src/log.js +177 -0
  93. package/src/migrations.d.ts +9 -0
  94. package/src/migrations.js +111 -0
  95. package/src/models.js +45 -0
  96. package/src/permissions.d.ts +14 -0
  97. package/src/permissions.js +242 -0
  98. package/src/profiles.js +139 -0
  99. package/src/promptsafe.d.ts +1 -0
  100. package/src/promptsafe.js +19 -0
  101. package/src/providers.js +122 -0
  102. package/src/resolver.js +238 -0
  103. package/src/safe-path.js +20 -0
  104. package/src/shell.js +28 -0
  105. package/src/state-schema.d.ts +9 -0
  106. package/src/state-schema.js +60 -0
  107. package/src/task-rows.js +44 -0
  108. package/src/tasks.d.ts +128 -0
  109. package/src/tasks.js +881 -0
  110. package/src/teams.js +170 -0
  111. package/src/template-path.js +8 -0
  112. package/src/trust.js +59 -0
  113. package/src/types.d.ts +51 -0
  114. package/src/upgrade.js +195 -0
  115. package/src/user-settings.d.ts +6 -0
  116. package/src/user-settings.js +76 -0
  117. package/src/validation.d.ts +17 -0
  118. package/src/validation.js +302 -0
  119. package/src/verify.d.ts +15 -0
  120. package/src/verify.js +127 -0
  121. package/src/workflow.js +28 -0
  122. package/templates/ALP.md +52 -0
  123. package/templates/agents/lead/AGENT.md +90 -0
  124. package/templates/agents/main/AGENT.md +150 -0
  125. package/templates/agents/oracle/AGENT.md +44 -0
  126. package/templates/agents/peer/AGENT.md +55 -0
  127. package/templates/agents/reviewer/AGENT.md +80 -0
  128. package/templates/agents/supervisor/AGENT.md +72 -0
  129. package/templates/providers/antigravity.json +6 -0
  130. package/templates/providers/opencode.json +7 -0
  131. package/templates/role-skills.json +8 -0
  132. package/templates/skills/bug-loop/SKILL.md +44 -0
  133. package/templates/skills/bug-loop/references/test-proof.md +25 -0
  134. package/templates/skills/goal-griller/SKILL.md +49 -0
  135. package/templates/skills/prompt-leverage/SKILL.md +37 -0
  136. package/templates/skills/prompt-leverage/references/brief-template.md +27 -0
  137. package/templates/skills/sequence-execution-plan/SKILL.md +42 -0
  138. package/templates/skills/sequence-execution-plan/references/plan-template.md +22 -0
  139. package/templates/skills/smart-commits/SKILL.md +40 -0
  140. package/templates/skills/xia/SKILL.md +42 -0
  141. package/templates/teams/cafe/HOUSE_RULES.md +1 -0
  142. package/templates/teams/cafe/team.json +15 -0
  143. package/templates/teams/pho/HOUSE_RULES.md +1 -0
  144. package/templates/teams/pho/team.json +14 -0
@@ -0,0 +1,229 @@
1
+ // packages/core/src/permissions.js
2
+ import path from "node:path";
3
+ import { randomUUID } from "node:crypto";
4
+ import { readFile, writeFile } from "node:fs/promises";
5
+ import { moveFile } from "./atomic.js";
6
+ import { AlpError } from "./errors.js";
7
+ var BASES = ["read-only", "workspace-write", "full-access"];
8
+ var RANK = { "read-only": 0, "workspace-write": 1, "full-access": 2 };
9
+ var ADVISORS = ["oracle", "reviewer", "supervisor"];
10
+ var MAX_RULES = 200;
11
+ var NAME = /^[\w.-]{1,64}$/;
12
+ var RULE = /^([A-Za-z][\w-]*)(?:\((.*)\))?$/s;
13
+ var TOOLS = ["Bash", "Read", "Edit", "Write", "MultiEdit", "NotebookEdit", "Glob", "Grep", "WebFetch", "WebSearch", "Skill"];
14
+ var own = (value, key) => Object.hasOwn(value, key) ? value[key] : void 0;
15
+ var setOwn = (value, key, entry) => Object.defineProperty(value, key, { value: entry, enumerable: true, writable: true, configurable: true });
16
+ var object = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
17
+ var fail = (source, message) => {
18
+ throw new AlpError("INVALID_SETTINGS", `${source}: ${message}`);
19
+ };
20
+ function parseRule(rule, source = "rule") {
21
+ if (typeof rule !== "string" || rule.length > 500) fail(source, "a rule is text of at most 500 characters");
22
+ const match = RULE.exec(rule.trim());
23
+ if (!match) fail(source, `${JSON.stringify(rule)} is not Tool or Tool(specifier)`);
24
+ const [, tool, specifier] = match;
25
+ if (!TOOLS.includes(tool) && !/^mcp__[\w-]+(__[\w-]+)?$/.test(tool)) fail(source, `${JSON.stringify(rule)} names unknown tool ${tool}; use ${TOOLS.join(", ")} or mcp__<server>__<tool>`);
26
+ if (specifier !== void 0 && !specifier.trim()) fail(source, `${JSON.stringify(rule)} has an empty specifier`);
27
+ return { tool, ...specifier !== void 0 && specifier.trim() !== "*" ? { specifier: specifier.trim() } : {} };
28
+ }
29
+ function validatePermissions(value, source) {
30
+ if (value === void 0) return { profiles: {}, agents: {} };
31
+ if (!object(value)) fail(source, "permissions must be an object");
32
+ for (const key of Object.keys(value)) if (!["profiles", "agents"].includes(key)) fail(source, `unsupported permissions field '${key}'`);
33
+ const profiles = {};
34
+ if (value.profiles !== void 0) {
35
+ if (!object(value.profiles)) fail(source, "permissions.profiles must be an object");
36
+ for (const [name, profile] of Object.entries(value.profiles)) {
37
+ const where = `permissions.profiles.${name}`;
38
+ if (!NAME.test(name)) fail(source, `${where}: a profile name has letters, digits, dots, dashes and underscores`);
39
+ if (!object(profile)) fail(source, `${where} must be an object`);
40
+ for (const key of Object.keys(profile)) if (!["base", "allow", "ask", "deny", "beyondMode", "workdir"].includes(key)) fail(source, `${where}: unsupported field '${key}'`);
41
+ if (profile.workdir !== void 0 && profile.workdir !== "copy") fail(source, `${where}.workdir must be copy`);
42
+ if (profile.workdir === "copy" && profile.base !== void 0 && profile.base !== "read-only") fail(source, `${where}: workdir copy is for read-only profiles; writers use worktree isolation`);
43
+ if (profile.base !== void 0 && !BASES.includes(profile.base)) fail(source, `${where}.base must be ${BASES.join(", ")}`);
44
+ if (profile.beyondMode !== void 0 && !["refuse", "ask"].includes(profile.beyondMode)) fail(source, `${where}.beyondMode must be refuse or ask`);
45
+ const rules = {};
46
+ for (const kind of ["allow", "ask", "deny"]) {
47
+ const list = profile[kind] ?? [];
48
+ if (!Array.isArray(list) || list.length > MAX_RULES) fail(source, `${where}.${kind} must list at most ${MAX_RULES} rules`);
49
+ for (const rule of list) parseRule(rule, `${source}: ${where}.${kind}`);
50
+ rules[kind] = list.map((rule) => rule.trim());
51
+ }
52
+ setOwn(profiles, name, { ...profile.base ? { base: profile.base } : {}, ...profile.beyondMode ? { beyondMode: profile.beyondMode } : {}, ...profile.workdir ? { workdir: profile.workdir } : {}, ...rules });
53
+ }
54
+ }
55
+ const agents = {};
56
+ if (value.agents !== void 0) {
57
+ if (!object(value.agents)) fail(source, "permissions.agents must be an object");
58
+ for (const [agent, profile] of Object.entries(value.agents)) {
59
+ if (typeof profile !== "string" || !NAME.test(profile)) fail(source, `permissions.agents.${agent} must name a profile`);
60
+ setOwn(agents, agent, profile);
61
+ }
62
+ }
63
+ return { profiles, agents };
64
+ }
65
+ async function settingsPermissions(file) {
66
+ let text;
67
+ try {
68
+ text = await readFile(file, "utf8");
69
+ } catch (error) {
70
+ if (error.code === "ENOENT") return { profiles: {}, agents: {} };
71
+ throw error;
72
+ }
73
+ let settings;
74
+ try {
75
+ settings = JSON.parse(text);
76
+ } catch (error) {
77
+ fail(file, `invalid JSON: ${error.message}`);
78
+ }
79
+ if (!object(settings)) fail(file, "expected an object");
80
+ return validatePermissions(settings.permissions, file);
81
+ }
82
+ async function profileFor(projectRoot, home, agent) {
83
+ const project = await settingsPermissions(path.join(projectRoot, ".alp", "settings.json"));
84
+ const user = home ? await settingsPermissions(path.join(home, "settings.json")) : { profiles: {}, agents: {} };
85
+ const name = own(project.agents, agent) ?? own(user.agents, agent);
86
+ const advisor = ADVISORS.includes(agent);
87
+ if (!name) return advisor ? { name: "read-only", base: "read-only", allow: [], ask: [], deny: [], beyondMode: "refuse" } : null;
88
+ const defined = [own(project.profiles, name), own(user.profiles, name)].filter(Boolean);
89
+ if (!defined.length && !BASES.includes(name)) {
90
+ throw new AlpError("INVALID_SETTINGS", `permissions.agents.${agent} names profile '${name}', which no settings file defines`);
91
+ }
92
+ const base = defined.find((profile) => profile.base)?.base ?? (BASES.includes(name) ? name : advisor ? "read-only" : "full-access");
93
+ const copy = defined.some((profile) => profile.workdir === "copy");
94
+ if (copy && base !== "read-only") throw new AlpError("INVALID_SETTINGS", `Profile '${name}' has workdir copy, which needs base read-only`);
95
+ if (advisor && base !== "read-only") throw new AlpError("INVALID_SETTINGS", `${agent} is an advisor; its profile '${name}' must have base read-only`);
96
+ return {
97
+ name,
98
+ base,
99
+ allow: [...new Set(defined.flatMap((profile) => profile.allow ?? []))],
100
+ ask: [...new Set(defined.flatMap((profile) => profile.ask ?? []))],
101
+ deny: [...new Set(defined.flatMap((profile) => profile.deny ?? []))],
102
+ beyondMode: defined.find((profile) => profile.beyondMode)?.beyondMode ?? "refuse",
103
+ ...copy ? { workdir: "copy" } : {}
104
+ };
105
+ }
106
+ async function addAllowRule(projectRoot, home, profileName, rule) {
107
+ parseRule(rule);
108
+ for (const file of [path.join(projectRoot, ".alp", "settings.json"), ...home ? [path.join(home, "settings.json")] : []]) {
109
+ let settings;
110
+ try {
111
+ settings = JSON.parse(await readFile(file, "utf8"));
112
+ } catch (error) {
113
+ if (error.code === "ENOENT") continue;
114
+ throw error;
115
+ }
116
+ const profile = settings?.permissions?.profiles?.[profileName];
117
+ if (!object(profile)) continue;
118
+ profile.allow = [.../* @__PURE__ */ new Set([...profile.allow ?? [], rule])];
119
+ validatePermissions(settings.permissions, file);
120
+ const temporary = `${file}.${randomUUID().slice(0, 8)}.tmp`;
121
+ await writeFile(temporary, `${JSON.stringify(settings, null, 2)}
122
+ `);
123
+ await moveFile(temporary, file);
124
+ return file;
125
+ }
126
+ throw new AlpError("INVALID_SETTINGS", `No settings file defines profile ${profileName}`);
127
+ }
128
+ var capMode = (mode, base) => (RANK[mode] ?? 0) <= (RANK[base] ?? 0) ? mode : base;
129
+ function unwrapShell(command) {
130
+ const match = /^(?:\/\S*\/)?(?:ba|z|da)?sh\s+-l?c\s+(['"])([\s\S]*)\1\s*$/.exec(command.trim());
131
+ if (!match) return command.trim();
132
+ return match[1] === "'" ? match[2].replace(/'\\''/g, "'") : match[2].replace(/\\(["\\$`])/g, "$1");
133
+ }
134
+ function simpleCommands(line) {
135
+ const parts = [];
136
+ let current = "";
137
+ let quote = null;
138
+ for (let i = 0; i < line.length; i++) {
139
+ const char = line[i];
140
+ if (quote) {
141
+ if (char === "\\" && quote === '"') {
142
+ current += char + (line[++i] ?? "");
143
+ continue;
144
+ }
145
+ if (char === quote) quote = null;
146
+ if (quote === '"' && (char === "`" || char === "$" && line[i + 1] === "(")) return null;
147
+ current += char;
148
+ continue;
149
+ }
150
+ if (char === "\\") {
151
+ current += char + (line[++i] ?? "");
152
+ continue;
153
+ }
154
+ if (char === "'" || char === '"') {
155
+ quote = char;
156
+ current += char;
157
+ continue;
158
+ }
159
+ if (char === "`" || char === "$" && line[i + 1] === "(" || (char === "<" || char === ">") && line[i + 1] === "(") return null;
160
+ const two = line.slice(i, i + 2);
161
+ if (two === ">&" || two === "<&" || two === "&>") {
162
+ current += two;
163
+ i++;
164
+ continue;
165
+ }
166
+ if (two === "&&" || two === "||") {
167
+ parts.push(current);
168
+ current = "";
169
+ i++;
170
+ continue;
171
+ }
172
+ if (char === ";" || char === "|" || char === "\n" || char === "&") {
173
+ parts.push(current);
174
+ current = "";
175
+ continue;
176
+ }
177
+ current += char;
178
+ }
179
+ if (quote) return null;
180
+ parts.push(current);
181
+ return parts.map((part) => part.trim()).filter(Boolean);
182
+ }
183
+ function covers(specifier, command) {
184
+ if (specifier === void 0) return true;
185
+ const normalized = command.replace(/\s+/g, " ");
186
+ if (specifier.endsWith(":*") || specifier.endsWith(" *")) {
187
+ const prefix = specifier.slice(0, -2).trim().replace(/\s+/g, " ");
188
+ return normalized === prefix || normalized.startsWith(`${prefix} `);
189
+ }
190
+ if (specifier.endsWith("*")) return normalized.startsWith(specifier.slice(0, -1).replace(/\s+/g, " "));
191
+ return normalized === specifier.replace(/\s+/g, " ");
192
+ }
193
+ function redirects(command) {
194
+ const unquoted = command.replace(/'[^']*'|"(?:[^"\\]|\\.)*"/g, '""');
195
+ for (const match of unquoted.matchAll(/(?:\d*|&)>>?\s*(&?)(\S*)/g)) {
196
+ if (match[1] === "&" && /^\d+$/.test(match[2])) continue;
197
+ if (match[2] === "/dev/null") continue;
198
+ return true;
199
+ }
200
+ return false;
201
+ }
202
+ var bashRules = (rules) => rules.map((rule) => parseRule(rule)).filter((rule) => rule.tool === "Bash");
203
+ function commandDecision(profile, command) {
204
+ const line = unwrapShell(command);
205
+ const parts = simpleCommands(line);
206
+ const deny = bashRules(profile.deny);
207
+ if (deny.some((rule) => rule.specifier === void 0)) return "deny";
208
+ if (!parts) return deny.length ? "deny" : (profile.ask ?? []).length ? "ask" : void 0;
209
+ if (parts.some((part) => deny.some((rule) => covers(rule.specifier, part)))) return "deny";
210
+ const ask = bashRules(profile.ask ?? []);
211
+ if (ask.some((rule) => rule.specifier === void 0) || parts.some((part) => ask.some((rule) => covers(rule.specifier, part)))) return "ask";
212
+ const allow = bashRules(profile.allow);
213
+ if (parts.every((part) => !redirects(part) && allow.some((rule) => covers(rule.specifier, part)))) return "allow";
214
+ return void 0;
215
+ }
216
+ export {
217
+ ADVISORS,
218
+ BASES,
219
+ MAX_RULES,
220
+ TOOLS,
221
+ addAllowRule,
222
+ capMode,
223
+ commandDecision,
224
+ parseRule,
225
+ profileFor,
226
+ simpleCommands,
227
+ unwrapShell,
228
+ validatePermissions
229
+ };
@@ -0,0 +1,27 @@
1
+ /** Migrate complete choices while leaving legacy partial overrides readable. */
2
+ export function normalizeAgentProfiles(config: any, name: any): any;
3
+ /** Snapshot current user team choices into editable project profiles. */
4
+ export function projectProfiles({ root, library, templates, runtime, teamIds }?: {
5
+ library?: string | undefined;
6
+ }): Promise<{}>;
7
+ /** Apply per-project agent choices without replacing the team's graph or rules. */
8
+ export function applyProfile(team: any, profile: any): any;
9
+ /** Refresh only inherited profiles; older scaffolds are recognized by their shipped defaults. */
10
+ export function syncTemplateProfiles(settings: any, options?: {}): Promise<any>;
11
+ export function userLibrary(): string;
12
+ export function oracleProfiles(config: import("./types.js").AgentProfilesConfig | undefined): Array<import("./types.js").AgentModelConfig>;
13
+ export function agentProfiles(config: import("./types.js").AgentProfilesConfig | undefined, name: string): Array<import("./types.js").AgentModelConfig>;
14
+ export function parseProfile(profile: any): any;
15
+ export function profileString(profile: any): string;
16
+ export function modelEfforts(id: any): string[] | undefined;
17
+ export function fitEffort(id: any, effort: any): any;
18
+ export function anyModel(profile: any): boolean;
19
+ export function offers(profiles: any, model: any): any;
20
+ export function selectedProfile(profiles: Array<import("./types.js").AgentModelConfig>, model: string | undefined): import("./types.js").AgentModelConfig | undefined;
21
+ export function profileModel(config: any): any;
22
+ export function profileFingerprint(config: any, name: any): string;
23
+ export function profileDefaults(profiles: any): {
24
+ [k: string]: {
25
+ [k: string]: string;
26
+ };
27
+ };
@@ -0,0 +1,122 @@
1
+ // packages/core/src/profiles.js
2
+ import path from "node:path";
3
+ import os from "node:os";
4
+ import { createHash } from "node:crypto";
5
+ import { MODELS } from "./models.js";
6
+ import { resolveTeam, teamSources } from "./teams.js";
7
+ var userLibrary = () => process.env.ALP_HOME ?? path.join(os.homedir(), ".alp");
8
+ var oracleProfiles = (config) => config?.profiles ?? [config?.claude, config?.codex].filter(Boolean);
9
+ var agentProfiles = (config, name) => {
10
+ if (!config) return [];
11
+ if (name === "oracle") return oracleProfiles(config).map(parseProfile);
12
+ const { autocompact, ...legacy } = config;
13
+ return (config.profiles ?? [legacy]).map(parseProfile);
14
+ };
15
+ var parseProfile = (profile) => {
16
+ if (typeof profile !== "string") return profile;
17
+ const [provider, model, effort] = profile.split(":");
18
+ return { provider, model, ...effort ? { effort } : {} };
19
+ };
20
+ var profileString = (profile) => `${profileModel(profile)}${profile.effort ? `:${profile.effort}` : ""}`;
21
+ var modelEfforts = (id) => MODELS.find((model) => `${model.runtime}:${model.id}` === id)?.efforts;
22
+ var fitEffort = (id, effort) => {
23
+ const efforts = modelEfforts(id);
24
+ if (!efforts?.length || efforts.includes(effort)) return effort;
25
+ return MODELS.find((model) => `${model.runtime}:${model.id}` === id)?.effort ?? efforts[0];
26
+ };
27
+ var anyModel = (profile) => profile?.model === "*";
28
+ var offers = (profiles, model) => profiles.some((profile) => anyModel(profile) ? model.startsWith(`${profile.provider}:`) && model.length > profile.provider.length + 1 : profileModel(profile) === model);
29
+ var selectedProfile = (profiles, model) => model === void 0 ? profiles.find((config) => !anyModel(config)) : profiles.find((config) => !anyModel(config) && (!config.model || profileModel(config) === model || !model.includes(":") && config.model === model));
30
+ function normalizeAgentProfiles(config, name) {
31
+ if (config.profiles) return { ...config, profiles: config.profiles.map((profile) => typeof profile === "string" ? profile : profileString(profile)), autocompact: config.autocompact ?? "auto" };
32
+ const profiles = agentProfiles(config, name);
33
+ if (!profiles.length || profiles.some((profile) => !profile.model)) return config;
34
+ return { profiles: profiles.map((profile) => profileString({ ...profile, provider: profile.provider ?? profile.model.split(":")[0] })), autocompact: config.autocompact ?? "auto" };
35
+ }
36
+ var profileModel = (config) => config?.model ? config.model.includes(":") ? config.model : `${config.provider}:${config.model}` : void 0;
37
+ var withEffort = (model, effort) => {
38
+ if (model.startsWith("acp:") || model.startsWith("antigravity:")) return model;
39
+ const fitted = fitEffort(model, effort);
40
+ return fitted ? `${model}:${fitted}` : model;
41
+ };
42
+ async function projectProfiles({ root, library = userLibrary(), templates, runtime, teamIds } = {}) {
43
+ const profiles = {};
44
+ for (const id of (await teamSources(root, { library, templates })).keys()) {
45
+ if (teamIds && !teamIds.includes(id)) continue;
46
+ const team = await resolveTeam(root, id, { library, templates });
47
+ const fallback = team.members[team.main].model ?? "codex:gpt-6.1-sol";
48
+ const profile = {};
49
+ for (const [name, member] of Object.entries(team.members)) {
50
+ if (name === "oracle") continue;
51
+ const chosen = runtime?.model ? runtime.model.includes(":") ? runtime.model : `${runtime.provider ?? "codex"}:${runtime.model}` : runtime?.provider ? runtime.provider === "claude" ? "claude:claude-opus-5-5" : "codex:gpt-6.1-sol" : member.model ?? fallback;
52
+ profile[name] = { profiles: [withEffort(chosen, runtime?.reasoning ?? member.thinking ?? team.members[team.main].thinking ?? "medium")], autocompact: "auto" };
53
+ }
54
+ if (team.supervisor) profile[team.supervisor.agent] = { profiles: [withEffort(team.supervisor.model ?? fallback, team.supervisor.thinking ?? "medium")], autocompact: "auto" };
55
+ if (team.members.oracle) profile.oracle = { profiles: (team.oracleModels ?? ["claude:claude-fable-5-1", "codex:gpt-6-astra"]).map((model) => withEffort(model, team.oracleThinking?.[model] ?? team.oracleThinking?.[model.split(":")[0]] ?? "high")), autocompact: "auto" };
56
+ profiles[id] = profile;
57
+ }
58
+ return profiles;
59
+ }
60
+ function applyProfile(team, profile) {
61
+ if (!profile) return team;
62
+ const next = structuredClone(team);
63
+ for (const [name, config] of Object.entries(profile)) {
64
+ if (name === "oracle") {
65
+ const lenses = oracleProfiles(config).map(parseProfile);
66
+ next.oracleModels = lenses.map(profileModel);
67
+ const efforts = lenses.filter((lens) => lens.effort);
68
+ if (efforts.length) next.oracleThinking = { ...next.oracleThinking, ...Object.fromEntries(efforts.map((lens) => [profileModel(lens), lens.effort])) };
69
+ } else {
70
+ const target = next.members[name] ?? (next.supervisor && next.supervisor.agent === name ? next.supervisor : void 0);
71
+ if (!target) throw new Error(`team.${team.id}.${name}: agent is not in this team`);
72
+ const first = agentProfiles(config, name).find((profile2) => !anyModel(profile2));
73
+ if (first?.model) target.model = profileModel(first);
74
+ if (first?.effort) target.thinking = first.effort;
75
+ }
76
+ }
77
+ return next;
78
+ }
79
+ var profileFingerprint = (config, name) => createHash("sha256").update(JSON.stringify(agentProfiles(config, name).map(profileString))).digest("hex");
80
+ var profileDefaults = (profiles) => Object.fromEntries(Object.entries(profiles).map(([team, agents]) => [team, Object.fromEntries(Object.entries(agents).map(([name, config]) => [name, profileFingerprint(config, name)]))]));
81
+ async function syncTemplateProfiles(settings, options = {}) {
82
+ const defaults = await projectProfiles({ ...options, runtime: options.replaceModels ? void 0 : settings.runtime });
83
+ const legacy = settings.templateDefaults ? void 0 : await projectProfiles({ templates: options.templates, library: "", runtime: settings.runtime });
84
+ const previous = settings.templateDefaults ?? profileDefaults(legacy);
85
+ const next = structuredClone(settings);
86
+ next.team = { ...next.profile ?? {}, ...next.team ?? {} };
87
+ delete next.profile;
88
+ for (const id of /* @__PURE__ */ new Set([...Object.keys(previous), ...Object.keys(defaults)])) {
89
+ if (options.teamIds && !options.teamIds.includes(id)) continue;
90
+ const desired = defaults[id] ?? {};
91
+ const current = next.team[id] ?? {};
92
+ for (const name of /* @__PURE__ */ new Set([...Object.keys(previous[id] ?? {}), ...options.replaceModels ? Object.keys(current) : [], ...Object.keys(desired)])) {
93
+ const config = current[name];
94
+ if (!options.replaceModels && config && profileFingerprint(config, name) !== previous[id]?.[name]) continue;
95
+ if (desired[name]) current[name] = { ...desired[name], autocompact: config?.autocompact ?? desired[name].autocompact };
96
+ else delete current[name];
97
+ }
98
+ if (Object.keys(current).length) next.team[id] = current;
99
+ else delete next.team[id];
100
+ }
101
+ next.templateDefaults = options.teamIds ? { ...previous, ...profileDefaults(defaults) } : profileDefaults(defaults);
102
+ return next;
103
+ }
104
+ export {
105
+ agentProfiles,
106
+ anyModel,
107
+ applyProfile,
108
+ fitEffort,
109
+ modelEfforts,
110
+ normalizeAgentProfiles,
111
+ offers,
112
+ oracleProfiles,
113
+ parseProfile,
114
+ profileDefaults,
115
+ profileFingerprint,
116
+ profileModel,
117
+ profileString,
118
+ projectProfiles,
119
+ selectedProfile,
120
+ syncTemplateProfiles,
121
+ userLibrary
122
+ };
@@ -0,0 +1 @@
1
+ export function promptSafe<T>(text: T): T;
@@ -0,0 +1,13 @@
1
+ // packages/core/src/promptsafe.js
2
+ var TAG = /<\s*\/?\s*system-reminder\b[^>]*>/gi;
3
+ function promptSafe(text) {
4
+ if (typeof text !== "string") return text;
5
+ for (let current = text; ; ) {
6
+ const next = current.replace(TAG, "");
7
+ if (next === current) return next;
8
+ current = next;
9
+ }
10
+ }
11
+ export {
12
+ promptSafe
13
+ };
@@ -0,0 +1,90 @@
1
+ /** Checks a provider definition. */
2
+ export function validateProvider(config: any, source: any, id: any): any;
3
+ /**
4
+ * A provider of the library, with its id; undefined when there is none.
5
+ * @returns {Promise<{ id: string, kind: 'acp' | 'antigravity', label: string, description?: string, command: string, args: string[], env: Record<string, string>, models?: Array<{ id: string, label?: string, description?: string }>, additionalModels?: Array<{ id: string, label?: string, description?: string }>, enabled?: boolean, params?: Record<string, unknown>, readOnlyMode?: 'instructions' | 'reject' } | undefined>}
6
+ */
7
+ export function loadProvider(library: any, id: any): Promise<{
8
+ id: string;
9
+ kind: "acp" | "antigravity";
10
+ label: string;
11
+ description?: string;
12
+ command: string;
13
+ args: string[];
14
+ env: Record<string, string>;
15
+ models?: Array<{
16
+ id: string;
17
+ label?: string;
18
+ description?: string;
19
+ }>;
20
+ additionalModels?: Array<{
21
+ id: string;
22
+ label?: string;
23
+ description?: string;
24
+ }>;
25
+ enabled?: boolean;
26
+ params?: Record<string, unknown>;
27
+ readOnlyMode?: "instructions" | "reject";
28
+ } | undefined>;
29
+ /** Every provider of the library, by id. */
30
+ export function listProviders(library: any): Promise<{
31
+ id: string;
32
+ kind: "acp" | "antigravity";
33
+ label: string;
34
+ description?: string;
35
+ command: string;
36
+ args: string[];
37
+ env: Record<string, string>;
38
+ models?: Array<{
39
+ id: string;
40
+ label?: string;
41
+ description?: string;
42
+ }>;
43
+ additionalModels?: Array<{
44
+ id: string;
45
+ label?: string;
46
+ description?: string;
47
+ }>;
48
+ enabled?: boolean;
49
+ params?: Record<string, unknown>;
50
+ readOnlyMode?: "instructions" | "reject";
51
+ }[]>;
52
+ /**
53
+ * Splits an ACP model id, `<provider>` or `<provider>/<model>`, as resolveSession keeps it
54
+ * after the `acp:` prefix.
55
+ */
56
+ export function acpModel(model: any): {
57
+ provider: any;
58
+ model: any;
59
+ };
60
+ /**
61
+ * How to start a command without a shell. On Windows a bare name installed with npm is a
62
+ * `<name>.cmd` shim, which spawn refuses to run without one; its target runs instead: the
63
+ * native executable it calls, or the Node script, under this Node. A bare name with an .exe or
64
+ * .com on PATH, any other command, and every command elsewhere are returned as they are.
65
+ * @param {string} command @param {string[]} [args] @param {Record<string, string | undefined>} [env]
66
+ * @returns {{ command: string, args: string[] }}
67
+ */
68
+ export function launchCommand(command: string, args?: string[], env?: Record<string, string | undefined>): {
69
+ command: string;
70
+ args: string[];
71
+ };
72
+ /**
73
+ * ACP providers (ALPD §46): agents that speak the Agent Client Protocol, such as Gemini
74
+ * CLI or opencode, defined in the user's library as providers/<id>.json. An agent runs
75
+ * on one with `provider: "<id>"` in its agent.json, or with the model `acp:<id>` or
76
+ * `acp:<id>/<model>`. Providers live only in the library: a project's would run a
77
+ * command from its repository on the user's machine.
78
+ *
79
+ * { "kind": "acp", "label": "Gemini", "command": "gemini", "args": ["--experimental-acp"],
80
+ * "env": {}, "models": [{ "id": "gemini-2.5-pro" }] }
81
+ *
82
+ * The web app's Providers screen (ALPD §65) adds `enabled` (false turns it off), `additionalModels`
83
+ * (added to the models the agent reports, where `models` replaces them) and `params`, which
84
+ * Paseo's catalog carries and ALP keeps without using.
85
+ */
86
+ export const PROVIDER_SETTINGS: string[];
87
+ /** Runtimes ALP has built in; no provider takes their names. */
88
+ export const BUILTIN_RUNTIMES: string[];
89
+ /** Names no provider takes: the runtimes, and `alp`, which the web app lists ALP's teams under. */
90
+ export const RESERVED_PROVIDERS: string[];
@@ -0,0 +1,101 @@
1
+ // packages/core/src/providers.js
2
+ import { existsSync, readFileSync } from "node:fs";
3
+ import { readdir, readFile } from "node:fs/promises";
4
+ import path from "node:path";
5
+ import { AlpError } from "./errors.js";
6
+ import { settingsKeys, unknownMessage } from "./validation.js";
7
+ var PROVIDER_SETTINGS = ["$schema", "kind", "label", "description", "command", "args", "env", "models", "additionalModels", "enabled", "params", "readOnlyMode"];
8
+ var BUILTIN_RUNTIMES = ["codex", "claude", "acp", "antigravity"];
9
+ var RESERVED_PROVIDERS = [...BUILTIN_RUNTIMES, "alp"];
10
+ var object = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
11
+ var nonempty = (value) => typeof value === "string" && value.trim().length > 0;
12
+ var validId = (id) => typeof id === "string" && /^[\w.-]+$/.test(id) && id !== "." && id !== "..";
13
+ function validateProvider(config, source, id) {
14
+ const check = (ok, message) => {
15
+ if (!ok) throw new AlpError("INVALID_PROVIDER", `${source}: ${message}`);
16
+ };
17
+ if (id !== void 0) check(validId(id) && (!RESERVED_PROVIDERS.includes(id) || id === "antigravity" && config?.kind === "antigravity"), `'${id}' cannot name a provider: ${RESERVED_PROVIDERS.join(", ")} are taken`);
18
+ check(object(config), "expected an object");
19
+ const { unknown } = settingsKeys(config, PROVIDER_SETTINGS);
20
+ check(!unknown.length, unknown.length ? unknownMessage(unknown[0], PROVIDER_SETTINGS) : "");
21
+ check(["acp", "antigravity"].includes(config.kind), 'kind must be "acp" or "antigravity"');
22
+ if (config.kind === "antigravity" && id !== void 0) check(id === "antigravity", "Antigravity must use provider ID antigravity");
23
+ if (config.readOnlyMode !== void 0) check(config.kind === "antigravity" && ["instructions", "reject"].includes(config.readOnlyMode), 'readOnlyMode is only for Antigravity and must be "instructions" or "reject"');
24
+ for (const key of ["label", "description"]) if (config[key] !== void 0) check(typeof config[key] === "string", `${key} must be a string`);
25
+ check(nonempty(config.command), "command must be a nonempty string");
26
+ if (config.args !== void 0) check(Array.isArray(config.args) && config.args.every((arg) => typeof arg === "string"), "args must be a list of strings");
27
+ if (config.env !== void 0) check(object(config.env) && Object.values(config.env).every((value) => typeof value === "string"), "env must map names to strings");
28
+ for (const key of ["models", "additionalModels"]) {
29
+ if (config[key] === void 0) continue;
30
+ check(Array.isArray(config[key]) && config[key].every((model) => object(model) && nonempty(model.id) && !/\s/.test(model.id) && Object.keys(model).every((field) => ["id", "label", "description"].includes(field)) && ["label", "description"].every((field) => model[field] === void 0 || typeof model[field] === "string")), `${key} must list { id, label?, description? }`);
31
+ check(new Set(config[key].map((model) => model.id)).size === config[key].length, `${key} lists an id twice`);
32
+ }
33
+ if (config.enabled !== void 0) check(typeof config.enabled === "boolean", "enabled must be true or false");
34
+ if (config.params !== void 0) check(object(config.params), "params must be an object");
35
+ return config;
36
+ }
37
+ async function loadProvider(library, id) {
38
+ if (!library || !validId(id) || RESERVED_PROVIDERS.includes(id) && id !== "antigravity") return void 0;
39
+ const file = path.join(library, "providers", `${id}.json`);
40
+ let text;
41
+ try {
42
+ text = await readFile(file, "utf8");
43
+ } catch (error) {
44
+ if (error.code === "ENOENT") return void 0;
45
+ throw error;
46
+ }
47
+ let config;
48
+ try {
49
+ config = JSON.parse(text);
50
+ } catch (cause) {
51
+ throw new AlpError("INVALID_PROVIDER", `${file}: ${cause.message}`, { cause });
52
+ }
53
+ validateProvider(config, file, id);
54
+ const command = /[\\/]/.test(config.command) ? path.resolve(path.dirname(file), config.command) : config.command;
55
+ return { id, ...config, label: config.label ?? id, command, args: config.args ?? [], env: config.env ?? {} };
56
+ }
57
+ async function listProviders(library) {
58
+ if (!library) return [];
59
+ let names = [];
60
+ try {
61
+ names = await readdir(path.join(library, "providers"));
62
+ } catch (error) {
63
+ if (error.code !== "ENOENT") throw error;
64
+ }
65
+ const providers = [];
66
+ for (const name of names.filter((name2) => name2.endsWith(".json")).sort()) {
67
+ const provider = await loadProvider(library, name.slice(0, -5));
68
+ if (provider) providers.push(provider);
69
+ }
70
+ return providers;
71
+ }
72
+ function acpModel(model) {
73
+ const slash = model.indexOf("/");
74
+ return slash < 0 ? { provider: model, model: void 0 } : { provider: model.slice(0, slash), model: model.slice(slash + 1) || void 0 };
75
+ }
76
+ function launchCommand(command, args = [], env = process.env) {
77
+ if (process.platform !== "win32" || /[\\/]/.test(command) || path.extname(command)) return { command, args };
78
+ const key = Object.keys(env).find((name) => name.toUpperCase() === "PATH");
79
+ for (const directory of (key ? env[key] ?? "" : "").split(";").filter(Boolean)) {
80
+ if ([".exe", ".com"].some((extension) => existsSync(path.join(directory, command + extension)))) break;
81
+ const shim = path.join(directory, `${command}.cmd`);
82
+ if (!existsSync(shim)) continue;
83
+ const text = readFileSync(shim, "utf8");
84
+ const script = /"%_prog%"((?:\s+[^\s"]+)*)\s+"%~?dp0%\\?([^"]+)"\s+%\*/i.exec(text);
85
+ if (script) return { command: process.execPath, args: [...script[1].split(/\s+/).filter(Boolean), path.join(directory, script[2]), ...args] };
86
+ const native = /^\s*"%~?dp0%\\?([^"]+\.(?:exe|com))"\s+%\*/im.exec(text);
87
+ if (native) return { command: path.join(directory, native[1]), args };
88
+ break;
89
+ }
90
+ return { command, args };
91
+ }
92
+ export {
93
+ BUILTIN_RUNTIMES,
94
+ PROVIDER_SETTINGS,
95
+ RESERVED_PROVIDERS,
96
+ acpModel,
97
+ launchCommand,
98
+ listProviders,
99
+ loadProvider,
100
+ validateProvider
101
+ };
@@ -0,0 +1,41 @@
1
+ export function optionalText(file: any): Promise<string | undefined>;
2
+ export function jsonObject(file: any, code: any): Promise<any>;
3
+ /** A shipped template file, from `templates` when given (bundles embed them), else from the package. */
4
+ export function builtinText(relative: any, templates: any): Promise<any>;
5
+ /**
6
+ * Where each agent comes from (ALPD §41): ALP's built-ins, then the user's library
7
+ * (`library`, ALP_HOME), then the project's `.alp/agents`. A later layer replaces an
8
+ * agent of the same name entirely.
9
+ * @returns {Promise<Map<string, { source: 'builtin' | 'library' | 'project', directory?: string, overrides?: 'builtin' | 'library' }>>}
10
+ */
11
+ export function agentSources(root: any, { library, templates }?: {}): Promise<Map<string, {
12
+ source: "builtin" | "library" | "project";
13
+ directory?: string;
14
+ overrides?: "builtin" | "library";
15
+ }>>;
16
+ export function discoverAgents(root: any, options?: {}): Promise<string[]>;
17
+ /**
18
+ * Every entry of one kind with where it comes from (ALPD §41): `agents`, `skills`, `mcp`
19
+ * or `hooks`. A project entry overrides a library one of the same name. `usedBy` lists
20
+ * the agents that name a skill, MCP server or hook; for skills it includes the roles
21
+ * role-skills.json gives it.
22
+ * @returns {Promise<Array<{ name: string, source: 'builtin' | 'library' | 'project', path?: string, overrides?: 'builtin' | 'library', description?: string, usedBy?: string[] }>>}
23
+ */
24
+ export function libraryEntries(kind: any, projectRoot: any, { library, templates }?: {}): Promise<Array<{
25
+ name: string;
26
+ source: "builtin" | "library" | "project";
27
+ path?: string;
28
+ overrides?: "builtin" | "library";
29
+ description?: string;
30
+ usedBy?: string[];
31
+ }>>;
32
+ /**
33
+ * `library` is the user's library (ALP_HOME): its agents and the skills, MCP servers and
34
+ * hooks agents name. The agent's own skills/ directory replaces a skill of the same name.
35
+ * @returns {Promise<import('./types.js').ResolvedAgent>}
36
+ */
37
+ export function resolveAgent(projectRoot: any, { agent, library, templates }?: {}): Promise<import("./types.js").ResolvedAgent>;
38
+ export { AlpError } from "./errors.js";
39
+ /** The agents ALP ships; their files come from the package's templates. */
40
+ export const BUILTIN_AGENTS: string[];
41
+ export const LIBRARY_KINDS: string[];