@anhlp/alp 0.0.1-beta.3 → 0.0.1-beta.6
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 +14 -410
- package/bin/alp.js +5 -0
- package/lib/launcher.js +136 -0
- package/package.json +19 -104
- package/dist/THIRD-PARTY-NOTICES.txt +0 -626
- package/dist/acp.js +0 -19903
- package/dist/alpd.js +0 -42558
- package/docs/modular-monorepo.md +0 -210
- package/packages/cli/README.md +0 -7
- package/packages/cli/dist/THIRD-PARTY-NOTICES.txt +0 -626
- package/packages/cli/dist/acp/agent.d.ts +0 -37
- package/packages/cli/dist/acp/agent.js +0 -489
- package/packages/cli/dist/acp/main.d.ts +0 -4
- package/packages/cli/dist/acp/main.js +0 -23
- package/packages/cli/dist/acp.js +0 -19903
- package/packages/cli/dist/alpd.js +0 -42558
- package/packages/cli/dist/backup.d.ts +0 -4
- package/packages/cli/dist/backup.js +0 -146
- package/packages/cli/dist/build-manifest.json +0 -142
- package/packages/cli/dist/cli.d.ts +0 -2
- package/packages/cli/dist/cli.js +0 -28
- package/packages/cli/dist/daemon.d.ts +0 -35
- package/packages/cli/dist/daemon.js +0 -126
- package/packages/cli/dist/index.d.ts +0 -1
- package/packages/cli/dist/index.js +0 -5
- package/packages/cli/dist/interfaces.d.ts +0 -16
- package/packages/cli/dist/interfaces.js +0 -96
- package/packages/cli/dist/library.d.ts +0 -140
- package/packages/cli/dist/library.js +0 -319
- package/packages/cli/dist/paths.d.ts +0 -3
- package/packages/cli/dist/paths.js +0 -18
- package/packages/cli/dist/projects.d.ts +0 -9
- package/packages/cli/dist/projects.js +0 -159
- package/packages/cli/dist/sessions.d.ts +0 -41
- package/packages/cli/dist/sessions.js +0 -447
- package/packages/cli/dist/tasks.d.ts +0 -29
- package/packages/cli/dist/tasks.js +0 -345
- package/packages/cli/dist/usage.d.ts +0 -3
- package/packages/cli/dist/usage.js +0 -72
- package/packages/client/README.md +0 -24
- package/packages/client/dist/acp-probe.d.ts +0 -44
- package/packages/client/dist/acp-probe.js +0 -37
- package/packages/client/dist/antigravity-probe.d.ts +0 -34
- package/packages/client/dist/antigravity-probe.js +0 -95
- package/packages/client/dist/command.d.ts +0 -5
- package/packages/client/dist/command.js +0 -33
- package/packages/client/dist/doctor.d.ts +0 -13
- package/packages/client/dist/doctor.js +0 -587
- package/packages/client/dist/index.d.ts +0 -116
- package/packages/client/dist/index.js +0 -500
- package/packages/client/dist/ipc.d.ts +0 -42
- package/packages/client/dist/ipc.js +0 -69
- package/packages/client/dist/library-test.d.ts +0 -94
- package/packages/client/dist/library-test.js +0 -37
- package/packages/client/dist/mcp-probe.d.ts +0 -24
- package/packages/client/dist/mcp-probe.js +0 -147
- package/packages/client/dist/portable.d.ts +0 -18
- package/packages/client/dist/portable.js +0 -27
- package/packages/client/dist/prompt.d.ts +0 -32
- package/packages/client/dist/prompt.js +0 -59
- package/packages/client/dist/render.d.ts +0 -6
- package/packages/client/dist/render.js +0 -120
- package/packages/client/dist/rpc/client.d.ts +0 -82
- package/packages/client/dist/rpc/client.js +0 -292
- package/packages/client/dist/rpc/errors.d.ts +0 -42
- package/packages/client/dist/rpc/errors.js +0 -34
- package/packages/client/dist/rpc/framing.d.ts +0 -21
- package/packages/client/dist/rpc/framing.js +0 -57
- package/packages/client/dist/rpc/handshake.d.ts +0 -19
- package/packages/client/dist/rpc/handshake.js +0 -8
- package/packages/client/dist/rpc/reconnect.d.ts +0 -97
- package/packages/client/dist/rpc/reconnect.js +0 -227
- package/packages/client/dist/rpc/timers.d.ts +0 -11
- package/packages/client/dist/rpc/timers.js +0 -11
- package/packages/client/dist/service.d.ts +0 -50
- package/packages/client/dist/service.js +0 -143
- package/packages/client/dist/setup.d.ts +0 -2
- package/packages/client/dist/setup.js +0 -146
- package/packages/client/dist/supervise.d.ts +0 -12
- package/packages/client/dist/supervise.js +0 -87
- package/packages/client/dist/websocket.d.ts +0 -51
- package/packages/client/dist/websocket.js +0 -190
- package/packages/core/README.md +0 -7
- package/packages/core/dist/adapter.d.ts +0 -20
- package/packages/core/dist/adapter.js +0 -30
- package/packages/core/dist/atomic.d.ts +0 -2
- package/packages/core/dist/atomic.js +0 -28
- package/packages/core/dist/backup.d.ts +0 -27
- package/packages/core/dist/backup.js +0 -582
- package/packages/core/dist/beads.d.ts +0 -65
- package/packages/core/dist/beads.js +0 -213
- package/packages/core/dist/delegation.d.ts +0 -2
- package/packages/core/dist/delegation.js +0 -32
- package/packages/core/dist/errors.d.ts +0 -4
- package/packages/core/dist/errors.js +0 -11
- package/packages/core/dist/formulas.d.ts +0 -25
- package/packages/core/dist/formulas.js +0 -201
- package/packages/core/dist/hook-run.d.ts +0 -44
- package/packages/core/dist/hook-run.js +0 -60
- package/packages/core/dist/hooks.d.ts +0 -35
- package/packages/core/dist/hooks.js +0 -30
- package/packages/core/dist/index.d.ts +0 -30
- package/packages/core/dist/index.js +0 -63
- package/packages/core/dist/init.d.ts +0 -30
- package/packages/core/dist/init.js +0 -89
- package/packages/core/dist/library-edit.d.ts +0 -93
- package/packages/core/dist/library-edit.js +0 -368
- package/packages/core/dist/library.d.ts +0 -28
- package/packages/core/dist/library.js +0 -108
- package/packages/core/dist/log.d.ts +0 -37
- package/packages/core/dist/log.js +0 -156
- package/packages/core/dist/migrations.d.ts +0 -9
- package/packages/core/dist/migrations.js +0 -95
- package/packages/core/dist/models.d.ts +0 -11
- package/packages/core/dist/models.js +0 -44
- package/packages/core/dist/permissions.d.ts +0 -14
- package/packages/core/dist/permissions.js +0 -229
- package/packages/core/dist/profiles.d.ts +0 -27
- package/packages/core/dist/profiles.js +0 -122
- package/packages/core/dist/promptsafe.d.ts +0 -1
- package/packages/core/dist/promptsafe.js +0 -13
- package/packages/core/dist/providers.d.ts +0 -90
- package/packages/core/dist/providers.js +0 -101
- package/packages/core/dist/resolver.d.ts +0 -41
- package/packages/core/dist/resolver.js +0 -240
- package/packages/core/dist/safe-path.d.ts +0 -2
- package/packages/core/dist/safe-path.js +0 -24
- package/packages/core/dist/shell.d.ts +0 -16
- package/packages/core/dist/shell.js +0 -22
- package/packages/core/dist/state-schema.d.ts +0 -9
- package/packages/core/dist/state-schema.js +0 -54
- package/packages/core/dist/task-rows.d.ts +0 -47
- package/packages/core/dist/task-rows.js +0 -45
- package/packages/core/dist/tasks.d.ts +0 -128
- package/packages/core/dist/tasks.js +0 -835
- package/packages/core/dist/teams.d.ts +0 -80
- package/packages/core/dist/teams.js +0 -159
- package/packages/core/dist/template-path.d.ts +0 -2
- package/packages/core/dist/template-path.js +0 -10
- package/packages/core/dist/trust.d.ts +0 -8
- package/packages/core/dist/trust.js +0 -47
- package/packages/core/dist/types.d.ts +0 -51
- package/packages/core/dist/upgrade.d.ts +0 -10
- package/packages/core/dist/upgrade.js +0 -200
- package/packages/core/dist/user-settings.d.ts +0 -6
- package/packages/core/dist/user-settings.js +0 -72
- package/packages/core/dist/validation.d.ts +0 -17
- package/packages/core/dist/validation.js +0 -295
- package/packages/core/dist/verify.d.ts +0 -15
- package/packages/core/dist/verify.js +0 -123
- package/packages/core/dist/workflow.d.ts +0 -45
- package/packages/core/dist/workflow.js +0 -33
- package/templates/ALP.md +0 -52
- package/templates/agents/lead/AGENT.md +0 -90
- package/templates/agents/main/AGENT.md +0 -150
- package/templates/agents/oracle/AGENT.md +0 -44
- package/templates/agents/peer/AGENT.md +0 -55
- package/templates/agents/reviewer/AGENT.md +0 -80
- package/templates/agents/supervisor/AGENT.md +0 -72
- package/templates/providers/antigravity.json +0 -6
- package/templates/providers/opencode.json +0 -7
- package/templates/role-skills.json +0 -8
- package/templates/skills/bug-loop/SKILL.md +0 -44
- package/templates/skills/bug-loop/references/test-proof.md +0 -25
- package/templates/skills/goal-griller/SKILL.md +0 -49
- package/templates/skills/prompt-leverage/SKILL.md +0 -37
- package/templates/skills/prompt-leverage/references/brief-template.md +0 -27
- package/templates/skills/sequence-execution-plan/SKILL.md +0 -42
- package/templates/skills/sequence-execution-plan/references/plan-template.md +0 -22
- package/templates/skills/smart-commits/SKILL.md +0 -40
- package/templates/skills/xia/SKILL.md +0 -42
- package/templates/teams/cafe/HOUSE_RULES.md +0 -1
- package/templates/teams/cafe/team.json +0 -15
- package/templates/teams/pho/HOUSE_RULES.md +0 -1
- package/templates/teams/pho/team.json +0 -14
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
export const PROJECT_SETTINGS: string[];
|
|
2
|
-
export const USER_SETTINGS: string[];
|
|
3
|
-
export const RETIRED_SETTINGS: { project: Record<string, string>; user: Record<string, string> };
|
|
4
|
-
export function settingsKeys(settings: unknown, known: string[], retired?: Record<string, string>): { unknown: Array<{ key: string; suggestion?: string }>; retired: Array<{ key: string; instead: string }> };
|
|
5
|
-
export function settingsWarnings(settings: unknown, scope: 'project' | 'user'): string[];
|
|
6
|
-
export function validateSettings(settings: unknown, source: string): { provider?: string; model?: string; reasoning?: string };
|
|
7
|
-
export function validateUserSettings(settings: unknown, source: string): { language?: string; permissions?: unknown; limits?: { autoResume?: boolean }; recovery?: { autoResume?: boolean } };
|
|
8
|
-
export function normalizeMcp(raw: unknown, directory: string, source: string): { mcpServers: Record<string, Record<string, unknown>> };
|
|
9
|
-
export function validateResolvedAgent(agent: unknown): unknown;
|
|
10
|
-
export const AGENT_SETTINGS: string[];
|
|
11
|
-
export const CONTEXT_TOKENS: { min: number; max: number };
|
|
12
|
-
export function validateAgentConfig(config: unknown, source: string): { description?: string; provider?: string; model?: string; thinking?: string; mode?: 'read-only' | 'workspace-write' | 'full-access'; skills?: string[]; mcp?: string[]; hooks?: string[] };
|
|
13
|
-
export const HOOK_SETTINGS: string[];
|
|
14
|
-
export const HOOK_EVENTS: string[];
|
|
15
|
-
export const BLOCKING_HOOK_EVENTS: string[];
|
|
16
|
-
export function validateHook(config: unknown, source: string): { description?: string; event: string; command: string; blocking?: boolean; timeoutSec?: number; match?: { agent?: string; label?: string } };
|
|
17
|
-
export function unknownMessage(unknown: { key: string; suggestion?: string }, known: string[]): string;
|
|
@@ -1,295 +0,0 @@
|
|
|
1
|
-
// packages/core/src/validation.js
|
|
2
|
-
import path from "node:path";
|
|
3
|
-
import { AlpError } from "./errors.js";
|
|
4
|
-
import { MODELS } from "./models.js";
|
|
5
|
-
import { validatePermissions } from "./permissions.js";
|
|
6
|
-
import { validateVerify } from "./verify.js";
|
|
7
|
-
var object = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
|
|
8
|
-
var nonempty = (value) => typeof value === "string" && value.trim().length > 0;
|
|
9
|
-
var stringMap = (value) => object(value) && Object.values(value).every((v) => typeof v === "string");
|
|
10
|
-
function requireValue(condition, code, source, message) {
|
|
11
|
-
if (!condition) throw new AlpError(code, `${source}: ${message}`);
|
|
12
|
-
}
|
|
13
|
-
var EFFORTS = ["none", "off", "low", "medium", "high", "xhigh", "max", "ultra", "ultracode"];
|
|
14
|
-
var PROJECT_SETTINGS = ["$schema", "defaultAgent", "workflow", "runtime", "permissions", "verify", "delegation", "team", "profile", "templateDefaults"];
|
|
15
|
-
var USER_SETTINGS = ["$schema", "language", "permissions", "limits", "recovery", "web"];
|
|
16
|
-
var RETIRED_SETTINGS = { project: {}, user: {} };
|
|
17
|
-
function distance(a, b) {
|
|
18
|
-
const row = Array.from({ length: b.length + 1 }, (_, index) => index);
|
|
19
|
-
for (let i = 1; i <= a.length; i++) {
|
|
20
|
-
let previous = row[0];
|
|
21
|
-
row[0] = i;
|
|
22
|
-
for (let j = 1; j <= b.length; j++) {
|
|
23
|
-
const kept = row[j];
|
|
24
|
-
row[j] = Math.min(row[j] + 1, row[j - 1] + 1, previous + (a[i - 1].toLowerCase() === b[j - 1].toLowerCase() ? 0 : 1));
|
|
25
|
-
previous = kept;
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
return row[b.length];
|
|
29
|
-
}
|
|
30
|
-
function settingsKeys(settings, known, retired = {}) {
|
|
31
|
-
const unknown = [];
|
|
32
|
-
const old = [];
|
|
33
|
-
for (const key of Object.keys(object(settings) ? settings : {})) {
|
|
34
|
-
if (known.includes(key)) continue;
|
|
35
|
-
if (Object.hasOwn(retired, key)) {
|
|
36
|
-
old.push({ key, instead: retired[key] });
|
|
37
|
-
continue;
|
|
38
|
-
}
|
|
39
|
-
const near = known.filter((candidate) => !candidate.startsWith("$")).map((candidate) => ({ candidate, d: distance(key, candidate) })).sort((a, b) => a.d - b.d)[0];
|
|
40
|
-
unknown.push({ key, ...near && near.d <= 2 ? { suggestion: near.candidate } : {} });
|
|
41
|
-
}
|
|
42
|
-
return { unknown, retired: old };
|
|
43
|
-
}
|
|
44
|
-
var unknownMessage = (unknown, known) => `unknown setting '${unknown.key}'${unknown.suggestion ? `; did you mean '${unknown.suggestion}'?` : `; known settings are ${known.filter((key) => !key.startsWith("$")).join(", ")}`}`;
|
|
45
|
-
function settingsWarnings(settings, scope) {
|
|
46
|
-
return settingsKeys(settings, scope === "user" ? USER_SETTINGS : PROJECT_SETTINGS, RETIRED_SETTINGS[scope]).retired.map(({ key, instead }) => `setting '${key}' is no longer read; ${instead}`);
|
|
47
|
-
}
|
|
48
|
-
function validateSettings(settings, source) {
|
|
49
|
-
const check = (ok, message) => requireValue(ok, "INVALID_SETTINGS", source, message);
|
|
50
|
-
check(object(settings), "expected an object");
|
|
51
|
-
const { unknown } = settingsKeys(settings, PROJECT_SETTINGS, RETIRED_SETTINGS.project);
|
|
52
|
-
check(!unknown.length, unknown.length ? unknownMessage(unknown[0], PROJECT_SETTINGS) : "");
|
|
53
|
-
if (settings.defaultAgent !== void 0) check(nonempty(settings.defaultAgent), "defaultAgent must be a nonempty string");
|
|
54
|
-
if (settings.workflow !== void 0) {
|
|
55
|
-
check(object(settings.workflow), "workflow must be an object");
|
|
56
|
-
check(Object.keys(settings.workflow).every((key) => ["mode", "maxPeers", "supervisor"].includes(key)), "unsupported workflow field");
|
|
57
|
-
if (settings.workflow.mode !== void 0) check(typeof settings.workflow.mode === "string" && /^[\w.-]+$/.test(settings.workflow.mode) && settings.workflow.mode !== "custom", "workflow.mode must name a team, such as pho or cafe");
|
|
58
|
-
if (settings.workflow.maxPeers !== void 0) check(Number.isSafeInteger(settings.workflow.maxPeers) && settings.workflow.maxPeers > 0, "workflow.maxPeers must be a positive integer");
|
|
59
|
-
if (settings.workflow.supervisor !== void 0) check(typeof settings.workflow.supervisor === "boolean", "workflow.supervisor must be true or false");
|
|
60
|
-
}
|
|
61
|
-
if (settings.templateDefaults !== void 0) {
|
|
62
|
-
check(object(settings.templateDefaults), "templateDefaults must map teams to profile fingerprints");
|
|
63
|
-
for (const [team, agents] of Object.entries(settings.templateDefaults)) {
|
|
64
|
-
check(/^[\w.-]+$/.test(team) && object(agents), "templateDefaults must map teams to profile fingerprints");
|
|
65
|
-
for (const [agent, hash] of Object.entries(agents)) check(/^[\w.-]+$/.test(agent) && typeof hash === "string" && /^[a-f0-9]{64}$/.test(hash), "templateDefaults requires profile fingerprints");
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
for (const key of ["team", "profile"]) {
|
|
69
|
-
if (settings[key] === void 0) continue;
|
|
70
|
-
check(object(settings[key]), `${key} must map team names to agent configurations`);
|
|
71
|
-
const validateChoice = (config, where) => {
|
|
72
|
-
check(object(config), `${where} must be an object`);
|
|
73
|
-
check(Object.keys(config).every((key2) => ["provider", "model", "effort"].includes(key2)), `${where}: use provider, model and effort`);
|
|
74
|
-
if (config.provider !== void 0) check(["codex", "claude", "antigravity", "acp"].includes(config.provider), `${where}.provider must be codex, claude or antigravity`);
|
|
75
|
-
if (config.model !== void 0) {
|
|
76
|
-
check(nonempty(config.model) && !/\s/.test(config.model), `${where}.model must be a model ID`);
|
|
77
|
-
check(config.provider !== void 0 || /^(codex|claude|antigravity|acp):.+$/.test(config.model), `${where}: model needs a provider`);
|
|
78
|
-
if (config.model.includes(":")) check(/^(codex|claude|antigravity|acp):.+$/.test(config.model) && (!config.provider || config.model.startsWith(`${config.provider}:`)), `${where}: model and provider disagree`);
|
|
79
|
-
}
|
|
80
|
-
if (config.effort !== void 0) check(EFFORTS.includes(config.effort), `${where}.effort is unsupported`);
|
|
81
|
-
};
|
|
82
|
-
for (const [team, agents] of Object.entries(settings[key])) {
|
|
83
|
-
check(/^[\w.-]+$/.test(team) && object(agents), `${key}.${team} must map agent names to configurations`);
|
|
84
|
-
for (const [agent, config] of Object.entries(agents)) {
|
|
85
|
-
const where = `${key}.${team}.${agent}`;
|
|
86
|
-
check(object(config), `${where} must be an object`);
|
|
87
|
-
if (config.autocompact !== void 0) validateAutoCompact(config.autocompact, check, `${where}.autocompact`);
|
|
88
|
-
if (Object.hasOwn(config, "profiles")) {
|
|
89
|
-
check(Object.keys(config).every((key2) => ["profiles", "autocompact"].includes(key2)), `${where}: use profiles and autocompact`);
|
|
90
|
-
check(Array.isArray(config.profiles) && config.profiles.length > 0, `${where}.profiles must be a nonempty list`);
|
|
91
|
-
const models = [];
|
|
92
|
-
for (const [index, entry] of config.profiles.entries()) {
|
|
93
|
-
if (typeof entry === "string") {
|
|
94
|
-
const at = `${where}.profiles[${index}]`;
|
|
95
|
-
check(/^(codex|claude|antigravity):(\*|[^\s:*]+(:[a-z]+)?)$/.test(entry) || /^acp:[\w.-]+(?:\/[^\s:*]+)?$/.test(entry), `${at} must be "provider:model", "provider:model:effort" (such as "claude:claude-opus-5-5:high"), "acp:provider/model", or "codex:*" / "claude:*" for any model of that provider`);
|
|
96
|
-
const [provider, model, effort] = entry.split(":");
|
|
97
|
-
check(model !== "*" || agent !== "oracle" && agent !== "main", `${at}: ${agent} runs on a named model, not any model`);
|
|
98
|
-
if (effort !== void 0) {
|
|
99
|
-
const known = MODELS.find((candidate) => candidate.runtime === provider && candidate.id === model);
|
|
100
|
-
check(EFFORTS.includes(effort), `${at}: effort ${effort} is unsupported`);
|
|
101
|
-
check(!known?.efforts.length || known.efforts.includes(effort), `${at}: ${provider}:${model} takes effort ${known?.efforts.join(", ")}`);
|
|
102
|
-
}
|
|
103
|
-
models.push(`${provider}:${model}`);
|
|
104
|
-
continue;
|
|
105
|
-
}
|
|
106
|
-
validateChoice(entry, `${where}.profiles[${index}]`);
|
|
107
|
-
check(["claude", "codex", "antigravity"].includes(entry.provider) && nonempty(entry.model), `${where}.profiles[${index}]: specify provider and model`);
|
|
108
|
-
models.push(entry.model.includes(":") ? entry.model : `${entry.provider}:${entry.model}`);
|
|
109
|
-
}
|
|
110
|
-
check(new Set(models).size === models.length, `${where}.profiles must not repeat the same model`);
|
|
111
|
-
check(agent !== "main" || config.profiles.length === 1, `${where}.profiles: main runs on exactly one model`);
|
|
112
|
-
} else if (agent === "oracle") {
|
|
113
|
-
const entries = Object.entries(config).filter(([key2]) => key2 !== "autocompact");
|
|
114
|
-
check(entries.length > 0 && entries.every(([provider]) => ["claude", "codex"].includes(provider)), `${where} must define profiles or claude/codex lenses`);
|
|
115
|
-
for (const [provider, lens] of entries) {
|
|
116
|
-
validateChoice(lens, `${where}.${provider}`);
|
|
117
|
-
check(lens.provider === provider && nonempty(lens.model), `${where}.${provider}: specify matching provider and model`);
|
|
118
|
-
}
|
|
119
|
-
} else {
|
|
120
|
-
const { autocompact, ...legacy } = config;
|
|
121
|
-
validateChoice(legacy, where);
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
validatePermissions(settings.permissions, source);
|
|
127
|
-
validateVerify(settings.verify, source);
|
|
128
|
-
const runtime = settings.runtime ?? {};
|
|
129
|
-
if (settings.runtime !== void 0) check(object(settings.runtime), "runtime must be an object");
|
|
130
|
-
for (const key of Object.keys(runtime)) {
|
|
131
|
-
check(["provider", "model", "reasoning"].includes(key), `unsupported runtime field '${key}'`);
|
|
132
|
-
check(nonempty(runtime[key]), `runtime.${key} must be a nonempty string`);
|
|
133
|
-
}
|
|
134
|
-
return { ...runtime };
|
|
135
|
-
}
|
|
136
|
-
function validateUserSettings(settings, source) {
|
|
137
|
-
const check = (ok, message) => requireValue(ok, "INVALID_SETTINGS", source, message);
|
|
138
|
-
check(object(settings), "expected an object");
|
|
139
|
-
const { unknown } = settingsKeys(settings, USER_SETTINGS, RETIRED_SETTINGS.user);
|
|
140
|
-
check(!unknown.length, unknown.length ? unknownMessage(unknown[0], USER_SETTINGS) : "");
|
|
141
|
-
for (const key of ["limits", "recovery"]) {
|
|
142
|
-
if (settings[key] === void 0) continue;
|
|
143
|
-
check(object(settings[key]), `${key} must be an object`);
|
|
144
|
-
const extra = Object.keys(settings[key]).find((field) => field !== "autoResume");
|
|
145
|
-
check(!extra, `unsupported ${key} field '${extra}'; use autoResume`);
|
|
146
|
-
if (settings[key].autoResume !== void 0) check(typeof settings[key].autoResume === "boolean", `${key}.autoResume must be true or false`);
|
|
147
|
-
}
|
|
148
|
-
if (settings.web !== void 0) {
|
|
149
|
-
check(object(settings.web), "web must be an object");
|
|
150
|
-
const extra = Object.keys(settings.web).find((field) => !["enabled", "port", "relay"].includes(field));
|
|
151
|
-
check(!extra, `unsupported web field '${extra}'; use enabled, port or relay`);
|
|
152
|
-
const relay = settings.web.relay;
|
|
153
|
-
if (relay !== void 0) {
|
|
154
|
-
check(object(relay), "web.relay must be an object");
|
|
155
|
-
const unsupported = Object.keys(relay).find((field) => !["enabled", "endpoint", "useTls"].includes(field));
|
|
156
|
-
check(!unsupported, `unsupported web.relay field '${unsupported}'; use enabled, endpoint or useTls`);
|
|
157
|
-
if (relay.enabled !== void 0) check(typeof relay.enabled === "boolean", "web.relay.enabled must be true or false");
|
|
158
|
-
if (relay.endpoint !== void 0) check(typeof relay.endpoint === "string" && /^[a-z0-9.-]+:\d{1,5}$/i.test(relay.endpoint), "web.relay.endpoint must be host:port, such as relay.paseo.sh:443");
|
|
159
|
-
if (relay.useTls !== void 0) check(typeof relay.useTls === "boolean", "web.relay.useTls must be true or false");
|
|
160
|
-
}
|
|
161
|
-
if (settings.web.enabled !== void 0) check(typeof settings.web.enabled === "boolean", "web.enabled must be true or false");
|
|
162
|
-
if (settings.web.port !== void 0) check(Number.isInteger(settings.web.port) && settings.web.port >= 1024 && settings.web.port <= 65535, "web.port must be a port from 1024 to 65535");
|
|
163
|
-
}
|
|
164
|
-
if (settings.language !== void 0) check(nonempty(settings.language) && settings.language.trim().length <= 40, "language must be a language name of at most 40 characters, such as Vietnamese or English");
|
|
165
|
-
validatePermissions(settings.permissions, source);
|
|
166
|
-
return settings;
|
|
167
|
-
}
|
|
168
|
-
var AGENT_SETTINGS = ["$schema", "description", "provider", "model", "thinking", "mode", "context", "autocompact", "skills", "mcp", "hooks"];
|
|
169
|
-
var CONTEXT_TOKENS = { min: 1e5, max: 1e6 };
|
|
170
|
-
function validateAutoCompact(value, check, where) {
|
|
171
|
-
check(
|
|
172
|
-
value === "auto" || Number.isSafeInteger(value) && value >= CONTEXT_TOKENS.min && value <= CONTEXT_TOKENS.max,
|
|
173
|
-
`${where} must be "auto" or a number of tokens from ${CONTEXT_TOKENS.min} to ${CONTEXT_TOKENS.max}`
|
|
174
|
-
);
|
|
175
|
-
}
|
|
176
|
-
var AGENT_MODES = ["read-only", "workspace-write", "full-access"];
|
|
177
|
-
var entryName = (value) => typeof value === "string" && /^[\w.-]+$/.test(value) && value !== "." && value !== "..";
|
|
178
|
-
var HOOK_SETTINGS = ["$schema", "description", "event", "command", "blocking", "timeoutSec", "match"];
|
|
179
|
-
var HOOK_EVENTS = ["session.start", "turn.end", "assignment.start", "assignment.end", "handoff", "task.close", "merge"];
|
|
180
|
-
var BLOCKING_HOOK_EVENTS = ["handoff", "task.close", "merge"];
|
|
181
|
-
function validateHook(config, source) {
|
|
182
|
-
const check = (ok, message) => requireValue(ok, "INVALID_HOOK", source, message);
|
|
183
|
-
check(object(config), "expected an object");
|
|
184
|
-
const { unknown } = settingsKeys(config, HOOK_SETTINGS);
|
|
185
|
-
check(!unknown.length, unknown.length ? unknownMessage(unknown[0], HOOK_SETTINGS) : "");
|
|
186
|
-
if (config.description !== void 0) check(typeof config.description === "string", "description must be a string");
|
|
187
|
-
check(HOOK_EVENTS.includes(config.event), `event must be one of ${HOOK_EVENTS.join(", ")}`);
|
|
188
|
-
check(nonempty(config.command), "command must be a nonempty string");
|
|
189
|
-
if (config.blocking !== void 0) {
|
|
190
|
-
check(typeof config.blocking === "boolean", "blocking must be true or false");
|
|
191
|
-
check(!config.blocking || BLOCKING_HOOK_EVENTS.includes(config.event), `only ${BLOCKING_HOOK_EVENTS.join(", ")} hooks can block`);
|
|
192
|
-
}
|
|
193
|
-
if (config.timeoutSec !== void 0) check(Number.isInteger(config.timeoutSec) && config.timeoutSec >= 1 && config.timeoutSec <= 3600, "timeoutSec must be a whole number of seconds from 1 to 3600");
|
|
194
|
-
if (config.match !== void 0) {
|
|
195
|
-
check(object(config.match) && Object.keys(config.match).every((key) => ["agent", "label"].includes(key)), "match may hold only agent and label");
|
|
196
|
-
for (const key of ["agent", "label"]) if (config.match[key] !== void 0) check(nonempty(config.match[key]), `match.${key} must be a nonempty string`);
|
|
197
|
-
}
|
|
198
|
-
return config;
|
|
199
|
-
}
|
|
200
|
-
function validateAgentConfig(config, source) {
|
|
201
|
-
const check = (ok, message) => requireValue(ok, "INVALID_AGENT_CONFIG", source, message);
|
|
202
|
-
check(object(config), "expected an object");
|
|
203
|
-
const { unknown } = settingsKeys(config, AGENT_SETTINGS);
|
|
204
|
-
check(!unknown.length, unknown.length ? unknownMessage(unknown[0], AGENT_SETTINGS) : "");
|
|
205
|
-
if (config.description !== void 0) check(typeof config.description === "string", "description must be a string");
|
|
206
|
-
for (const key of ["provider", "model", "thinking"]) if (config[key] !== void 0) check(nonempty(config[key]), `${key} must be a nonempty string`);
|
|
207
|
-
if (config.mode !== void 0) check(AGENT_MODES.includes(config.mode), `mode must be one of ${AGENT_MODES.join(", ")}`);
|
|
208
|
-
if (config.context !== void 0) {
|
|
209
|
-
check(
|
|
210
|
-
config.context === "auto" || Number.isSafeInteger(config.context) && config.context >= CONTEXT_TOKENS.min && config.context <= CONTEXT_TOKENS.max,
|
|
211
|
-
`context must be "auto" or a number of tokens from ${CONTEXT_TOKENS.min} to ${CONTEXT_TOKENS.max}`
|
|
212
|
-
);
|
|
213
|
-
}
|
|
214
|
-
if (config.autocompact !== void 0) validateAutoCompact(config.autocompact, check, "autocompact");
|
|
215
|
-
for (const key of ["skills", "mcp", "hooks"]) {
|
|
216
|
-
if (config[key] === void 0) continue;
|
|
217
|
-
check(Array.isArray(config[key]) && config[key].every(entryName), `${key} must list names of letters, digits, '.', '_' or '-'`);
|
|
218
|
-
check(new Set(config[key]).size === config[key].length, `${key} lists a name twice`);
|
|
219
|
-
}
|
|
220
|
-
return config;
|
|
221
|
-
}
|
|
222
|
-
function normalizeMcp(raw, directory, source) {
|
|
223
|
-
const check = (ok, message) => requireValue(ok, "INVALID_MCP", source, message);
|
|
224
|
-
check(object(raw), "expected an object");
|
|
225
|
-
check(Object.keys(raw).every((k) => k === "mcpServers"), "only mcpServers is supported");
|
|
226
|
-
const servers = raw.mcpServers === void 0 ? {} : raw.mcpServers;
|
|
227
|
-
check(object(servers), "mcpServers must be an object");
|
|
228
|
-
const normalized = [];
|
|
229
|
-
for (const [name, server] of Object.entries(servers)) {
|
|
230
|
-
check(nonempty(name) && object(server), `invalid server '${name}'`);
|
|
231
|
-
check(Object.keys(server).every((k) => ["command", "args", "cwd", "env", "url", "headers"].includes(k)), `${name}: unsupported server field`);
|
|
232
|
-
check(nonempty(server.command) !== nonempty(server.url), `${name}: specify exactly one of command or url`);
|
|
233
|
-
const result = { ...server };
|
|
234
|
-
if (server.command !== void 0) check(nonempty(server.command), `${name}.command must be nonempty`);
|
|
235
|
-
if (server.url !== void 0) {
|
|
236
|
-
check(nonempty(server.url), `${name}.url must be nonempty`);
|
|
237
|
-
let url;
|
|
238
|
-
try {
|
|
239
|
-
url = new URL(server.url);
|
|
240
|
-
} catch {
|
|
241
|
-
}
|
|
242
|
-
check(url && ["https:", "http:"].includes(url.protocol), `${name}.url must be an HTTP(S) URL`);
|
|
243
|
-
check(server.args === void 0 && server.cwd === void 0 && server.env === void 0, `${name}: args/cwd/env require command`);
|
|
244
|
-
}
|
|
245
|
-
if (server.args !== void 0) check(Array.isArray(server.args) && server.args.every((v) => typeof v === "string"), `${name}.args must be strings`);
|
|
246
|
-
for (const key of ["env", "headers"]) if (server[key] !== void 0) check(stringMap(server[key]), `${name}.${key} must map strings to strings`);
|
|
247
|
-
if (server.command !== void 0) check(server.headers === void 0, `${name}: headers require url`);
|
|
248
|
-
if (server.cwd !== void 0) {
|
|
249
|
-
check(nonempty(server.cwd), `${name}.cwd must be nonempty`);
|
|
250
|
-
result.cwd = path.resolve(directory, server.cwd);
|
|
251
|
-
}
|
|
252
|
-
if (server.command && /[\\/]/.test(server.command)) result.command = path.resolve(directory, server.command);
|
|
253
|
-
normalized.push([name, result]);
|
|
254
|
-
}
|
|
255
|
-
return { mcpServers: Object.fromEntries(normalized) };
|
|
256
|
-
}
|
|
257
|
-
function validateResolvedAgent(agent) {
|
|
258
|
-
const check = (ok, message) => requireValue(ok, "INVALID_RESOLVED_AGENT", "ResolvedAgent", message);
|
|
259
|
-
check(object(agent), "expected an object");
|
|
260
|
-
check(nonempty(agent.name), "name is required");
|
|
261
|
-
check(typeof agent.projectRoot === "string" && path.isAbsolute(agent.projectRoot), "projectRoot must be absolute");
|
|
262
|
-
check(object(agent.instructions) && typeof agent.instructions.project === "string" && typeof agent.instructions.agent === "string", "instructions must contain project and agent strings");
|
|
263
|
-
for (const key of ["skills", "hooks"]) {
|
|
264
|
-
check(Array.isArray(agent[key]), `${key} must be an array`);
|
|
265
|
-
for (const resource of agent[key]) check(object(resource) && nonempty(resource.name) && typeof resource.path === "string" && path.isAbsolute(resource.path), `${key} must contain named absolute paths`);
|
|
266
|
-
}
|
|
267
|
-
check(object(agent.runtime), "runtime must be an object");
|
|
268
|
-
validateSettings({ runtime: agent.runtime }, "ResolvedAgent.runtime");
|
|
269
|
-
normalizeMcp(agent.mcp, agent.projectRoot, "ResolvedAgent.mcp");
|
|
270
|
-
check(object(agent.mcp.mcpServers), "MCP must contain a normalized mcpServers map");
|
|
271
|
-
for (const server of Object.values(agent.mcp.mcpServers)) {
|
|
272
|
-
if (server.cwd !== void 0) check(path.isAbsolute(server.cwd), "MCP cwd must already be absolute");
|
|
273
|
-
if (server.command && /[\\/]/.test(server.command)) check(path.isAbsolute(server.command), "MCP command paths must already be absolute");
|
|
274
|
-
}
|
|
275
|
-
return agent;
|
|
276
|
-
}
|
|
277
|
-
export {
|
|
278
|
-
AGENT_SETTINGS,
|
|
279
|
-
BLOCKING_HOOK_EVENTS,
|
|
280
|
-
CONTEXT_TOKENS,
|
|
281
|
-
HOOK_EVENTS,
|
|
282
|
-
HOOK_SETTINGS,
|
|
283
|
-
PROJECT_SETTINGS,
|
|
284
|
-
RETIRED_SETTINGS,
|
|
285
|
-
USER_SETTINGS,
|
|
286
|
-
normalizeMcp,
|
|
287
|
-
settingsKeys,
|
|
288
|
-
settingsWarnings,
|
|
289
|
-
unknownMessage,
|
|
290
|
-
validateAgentConfig,
|
|
291
|
-
validateHook,
|
|
292
|
-
validateResolvedAgent,
|
|
293
|
-
validateSettings,
|
|
294
|
-
validateUserSettings
|
|
295
|
-
};
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
export const VERIFY_STEPS: readonly ['setup', 'typecheck', 'test'];
|
|
2
|
-
export const VERIFY_TIMEOUT_SEC: number;
|
|
3
|
-
export const VERIFY_GRACE_MS: number;
|
|
4
|
-
export const VERIFY_INFRA_EXIT: number;
|
|
5
|
-
|
|
6
|
-
export type VerifyConfig = { setup?: string; typecheck?: string; test?: string; timeoutSec: number; idleSec?: number };
|
|
7
|
-
|
|
8
|
-
export type VerifyCommand = { step: 'setup' | 'typecheck' | 'test'; command: string; exitCode: number; ms: number; output: string; timedOut?: boolean; idle?: boolean };
|
|
9
|
-
|
|
10
|
-
export type Verification = { passed: boolean; cwd: string; commands: VerifyCommand[]; skipped?: string };
|
|
11
|
-
|
|
12
|
-
export function validateVerify(verify: unknown, source: string): VerifyConfig | undefined;
|
|
13
|
-
export function verifyConfig(projectRoot: string): Promise<VerifyConfig | undefined>;
|
|
14
|
-
export function runVerify(cwd: string, config: VerifyConfig, options?: { env?: NodeJS.ProcessEnv }): Promise<Verification>;
|
|
15
|
-
export function describeVerification(verified: { passed: boolean; commands: Array<{ step: string; exitCode: number; timedOut?: boolean; idle?: boolean }>; skipped?: string }): string;
|
|
@@ -1,123 +0,0 @@
|
|
|
1
|
-
// packages/core/src/verify.js
|
|
2
|
-
import { readFile } from "node:fs/promises";
|
|
3
|
-
import path from "node:path";
|
|
4
|
-
import { AlpError } from "./errors.js";
|
|
5
|
-
import { signalShell, spawnShell } from "./shell.js";
|
|
6
|
-
var VERIFY_STEPS = ["setup", "typecheck", "test"];
|
|
7
|
-
var VERIFY_TIMEOUT_SEC = 600;
|
|
8
|
-
var VERIFY_GRACE_MS = 5e3;
|
|
9
|
-
var VERIFY_INFRA_EXIT = 75;
|
|
10
|
-
var COMMAND_CHARS = 2e3;
|
|
11
|
-
var OUTPUT_CHARS = 4e3;
|
|
12
|
-
var object = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
|
|
13
|
-
var fail = (source, message) => {
|
|
14
|
-
throw new AlpError("INVALID_SETTINGS", `${source}: ${message}`);
|
|
15
|
-
};
|
|
16
|
-
function validateVerify(verify, source) {
|
|
17
|
-
if (verify === void 0) return void 0;
|
|
18
|
-
if (!object(verify)) fail(source, "verify must be an object");
|
|
19
|
-
const extra = Object.keys(verify).filter((key) => ![...VERIFY_STEPS, "timeoutSec", "idleSec"].includes(key));
|
|
20
|
-
if (extra.length) fail(source, `unsupported verify field '${extra[0]}'; use ${VERIFY_STEPS.join(", ")}, timeoutSec and idleSec`);
|
|
21
|
-
for (const step of VERIFY_STEPS) {
|
|
22
|
-
if (verify[step] !== void 0 && (typeof verify[step] !== "string" || !verify[step].trim() || verify[step].length > COMMAND_CHARS)) fail(source, `verify.${step} must be a command of at most ${COMMAND_CHARS} characters`);
|
|
23
|
-
}
|
|
24
|
-
if (!VERIFY_STEPS.some((step) => verify[step] !== void 0)) fail(source, `verify needs at least one of ${VERIFY_STEPS.join(", ")}`);
|
|
25
|
-
if (verify.timeoutSec !== void 0 && (!Number.isSafeInteger(verify.timeoutSec) || verify.timeoutSec < 1 || verify.timeoutSec > 7200)) fail(source, "verify.timeoutSec must be 1 to 7200 seconds");
|
|
26
|
-
if (verify.idleSec !== void 0 && (!Number.isSafeInteger(verify.idleSec) || verify.idleSec < 1 || verify.idleSec > 7200)) fail(source, "verify.idleSec must be 1 to 7200 seconds");
|
|
27
|
-
return {
|
|
28
|
-
...Object.fromEntries(VERIFY_STEPS.filter((step) => verify[step] !== void 0).map((step) => [step, verify[step].trim()])),
|
|
29
|
-
timeoutSec: verify.timeoutSec ?? VERIFY_TIMEOUT_SEC,
|
|
30
|
-
...verify.idleSec !== void 0 ? { idleSec: verify.idleSec } : {}
|
|
31
|
-
};
|
|
32
|
-
}
|
|
33
|
-
async function verifyConfig(projectRoot) {
|
|
34
|
-
const file = path.join(projectRoot, ".alp", "settings.json");
|
|
35
|
-
let text;
|
|
36
|
-
try {
|
|
37
|
-
text = await readFile(file, "utf8");
|
|
38
|
-
} catch (error) {
|
|
39
|
-
if (error.code === "ENOENT") return void 0;
|
|
40
|
-
throw error;
|
|
41
|
-
}
|
|
42
|
-
let settings;
|
|
43
|
-
try {
|
|
44
|
-
settings = JSON.parse(text);
|
|
45
|
-
} catch (error) {
|
|
46
|
-
fail(file, `invalid JSON: ${error.message}`);
|
|
47
|
-
}
|
|
48
|
-
return object(settings) ? validateVerify(settings.verify, file) : void 0;
|
|
49
|
-
}
|
|
50
|
-
function runCommand(command, cwd, { timeoutMs, idleMs }, env) {
|
|
51
|
-
return new Promise((resolve) => {
|
|
52
|
-
const started = Date.now();
|
|
53
|
-
const child = spawnShell(command, { cwd, env, stdio: ["ignore", "pipe", "pipe"] });
|
|
54
|
-
let output = "";
|
|
55
|
-
let stopped;
|
|
56
|
-
let idle;
|
|
57
|
-
const signal = (name) => signalShell(child, name);
|
|
58
|
-
const stop = (reason) => {
|
|
59
|
-
if (stopped) return;
|
|
60
|
-
stopped = reason;
|
|
61
|
-
signal("SIGTERM");
|
|
62
|
-
setTimeout(() => signal("SIGKILL"), VERIFY_GRACE_MS).unref();
|
|
63
|
-
};
|
|
64
|
-
const quiet = () => {
|
|
65
|
-
if (!idleMs) return;
|
|
66
|
-
clearTimeout(idle);
|
|
67
|
-
idle = setTimeout(() => stop("idle"), idleMs);
|
|
68
|
-
};
|
|
69
|
-
const keep = (chunk) => {
|
|
70
|
-
output = (output + chunk).slice(-OUTPUT_CHARS * 2);
|
|
71
|
-
quiet();
|
|
72
|
-
};
|
|
73
|
-
child.stdout.on("data", keep);
|
|
74
|
-
child.stderr.on("data", keep);
|
|
75
|
-
quiet();
|
|
76
|
-
const timer = setTimeout(() => stop("timeout"), timeoutMs);
|
|
77
|
-
let done = false;
|
|
78
|
-
const finish = (exitCode, error) => {
|
|
79
|
-
if (done) return;
|
|
80
|
-
done = true;
|
|
81
|
-
clearTimeout(timer);
|
|
82
|
-
clearTimeout(idle);
|
|
83
|
-
const tail = output.length > OUTPUT_CHARS ? `\u2026${output.slice(-OUTPUT_CHARS)}` : output;
|
|
84
|
-
resolve({
|
|
85
|
-
exitCode,
|
|
86
|
-
ms: Date.now() - started,
|
|
87
|
-
output: error ? `${tail}${error}` : tail,
|
|
88
|
-
...stopped === "timeout" ? { timedOut: true } : stopped === "idle" ? { idle: true } : {},
|
|
89
|
-
...error ? { spawnError: true } : {}
|
|
90
|
-
});
|
|
91
|
-
};
|
|
92
|
-
child.once("error", (error) => finish(127, error.message));
|
|
93
|
-
child.once("close", (code, signal2) => finish(stopped ? 124 : code ?? (signal2 ? 128 : 1)));
|
|
94
|
-
});
|
|
95
|
-
}
|
|
96
|
-
async function runVerify(cwd, config, { env = process.env } = {}) {
|
|
97
|
-
const commands = [];
|
|
98
|
-
for (const step of VERIFY_STEPS) {
|
|
99
|
-
if (!config[step]) continue;
|
|
100
|
-
const { spawnError, ...result } = await runCommand(config[step], cwd, { timeoutMs: config.timeoutSec * 1e3, idleMs: config.idleSec ? config.idleSec * 1e3 : void 0 }, { ...env, ALP_VERIFY: "1" });
|
|
101
|
-
commands.push({ step, command: config[step], ...result });
|
|
102
|
-
if (spawnError || result.exitCode === VERIFY_INFRA_EXIT) {
|
|
103
|
-
return { passed: false, cwd, commands, skipped: `infra: ${step} could not run (${spawnError ? "the shell did not start" : `exit ${VERIFY_INFRA_EXIT}`})` };
|
|
104
|
-
}
|
|
105
|
-
if (result.exitCode !== 0) return { passed: false, cwd, commands };
|
|
106
|
-
}
|
|
107
|
-
return { passed: true, cwd, commands };
|
|
108
|
-
}
|
|
109
|
-
function describeVerification(verified) {
|
|
110
|
-
if (verified.skipped) return `verification skipped: ${verified.skipped}`;
|
|
111
|
-
const failed = verified.commands.find((command) => command.exitCode !== 0);
|
|
112
|
-
return verified.passed ? `verified (${verified.commands.map((command) => command.step).join(", ")})` : `verification failed: ${failed.step} ${failed.timedOut ? "timed out" : failed.idle ? "printed nothing for too long" : `exited ${failed.exitCode}`}`;
|
|
113
|
-
}
|
|
114
|
-
export {
|
|
115
|
-
VERIFY_GRACE_MS,
|
|
116
|
-
VERIFY_INFRA_EXIT,
|
|
117
|
-
VERIFY_STEPS,
|
|
118
|
-
VERIFY_TIMEOUT_SEC,
|
|
119
|
-
describeVerification,
|
|
120
|
-
runVerify,
|
|
121
|
-
validateVerify,
|
|
122
|
-
verifyConfig
|
|
123
|
-
};
|
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* The team a session runs in when the caller selects none (ALPD §42, §60): the one a resumed
|
|
3
|
-
* session ran in, else settings' workflow.mode, else `custom` (settings' delegation graph, no
|
|
4
|
-
* supervisor). A project without .alp/settings.json gets init's settings when a session is
|
|
5
|
-
* created, so with `scaffolded` a missing file counts as naming INIT_TEAM. session.create and
|
|
6
|
-
* session.preview both resolve through here, so a preview names the team that would run.
|
|
7
|
-
* @param {Record<string, any>} settings the project's settings, `{}` when the file is missing
|
|
8
|
-
* @param {{ restored?: { mode: string }, scaffolded?: boolean }} [options]
|
|
9
|
-
*/
|
|
10
|
-
export function defaultTeam(settings: Record<string, any>, { restored, scaffolded }?: {
|
|
11
|
-
restored?: {
|
|
12
|
-
mode: string;
|
|
13
|
-
};
|
|
14
|
-
scaffolded?: boolean;
|
|
15
|
-
}): any;
|
|
16
|
-
/**
|
|
17
|
-
* The team a session runs in (ALPD §42): the one the caller selects, else defaultTeam. Without
|
|
18
|
-
* any, the session is `custom`: its graph is settings' delegation and no supervisor watches it.
|
|
19
|
-
* Selecting `custom` is refused unless it is the team that would run anyway, so a client may send
|
|
20
|
-
* back the team session.preview named.
|
|
21
|
-
* @param {string} root
|
|
22
|
-
* @param {string | undefined} selected
|
|
23
|
-
* @param {{ mode: string, maxPeers?: number, supervisor?: boolean } | undefined} restored
|
|
24
|
-
* @param {{ library?: string, templates?: Record<string, string>, scaffold?: boolean }} [options]
|
|
25
|
-
* scaffold: a missing settings file will be written by init before the session runs (a preview).
|
|
26
|
-
* @returns {Promise<{ mode: string, maxPeers: number, supervisor: boolean, team?: Awaited<ReturnType<typeof resolveTeam>> }>}
|
|
27
|
-
*/
|
|
28
|
-
export function resolveWorkflow(root: string, selected: string | undefined, restored: {
|
|
29
|
-
mode: string;
|
|
30
|
-
maxPeers?: number;
|
|
31
|
-
supervisor?: boolean;
|
|
32
|
-
} | undefined, { library, templates, scaffold }?: {
|
|
33
|
-
library?: string;
|
|
34
|
-
templates?: Record<string, string>;
|
|
35
|
-
scaffold?: boolean;
|
|
36
|
-
}): Promise<{
|
|
37
|
-
mode: string;
|
|
38
|
-
maxPeers: number;
|
|
39
|
-
supervisor: boolean;
|
|
40
|
-
team?: Awaited<ReturnType<typeof resolveTeam>>;
|
|
41
|
-
}>;
|
|
42
|
-
export { teamOf as profileOf } from "./teams.js";
|
|
43
|
-
/** The team `alp init` names in a new project's settings (`init.js`). */
|
|
44
|
-
export const INIT_TEAM: "pho";
|
|
45
|
-
import { resolveTeam } from './teams.js';
|
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
// packages/core/src/workflow.js
|
|
2
|
-
import path from "node:path";
|
|
3
|
-
import { jsonObject, optionalText } from "./resolver.js";
|
|
4
|
-
import { applyProfile } from "./profiles.js";
|
|
5
|
-
import { validateSettings } from "./validation.js";
|
|
6
|
-
import { resolveTeam, teamOf } from "./teams.js";
|
|
7
|
-
import { teamOf as teamOf2 } from "./teams.js";
|
|
8
|
-
var INIT_TEAM = "pho";
|
|
9
|
-
function defaultTeam(settings, { restored, scaffolded = false } = {}) {
|
|
10
|
-
return teamOf(restored?.mode ?? settings.workflow?.mode ?? (scaffolded ? INIT_TEAM : "custom"));
|
|
11
|
-
}
|
|
12
|
-
async function resolveWorkflow(root, selected, restored, { library, templates, scaffold = false } = {}) {
|
|
13
|
-
const file = path.join(root, ".alp/settings.json");
|
|
14
|
-
const settings = await jsonObject(file, "INVALID_SETTINGS");
|
|
15
|
-
validateSettings(settings, ".alp/settings.json");
|
|
16
|
-
const scaffolded = scaffold && await optionalText(file) === void 0;
|
|
17
|
-
const fallback = defaultTeam(settings, { restored, scaffolded });
|
|
18
|
-
const chosen = selected === void 0 ? void 0 : teamOf(selected);
|
|
19
|
-
if (chosen === "custom" && fallback !== "custom") throw new Error("Choose a team, such as pho (Ph\u1EDF) or cafe (Cafe)");
|
|
20
|
-
const mode = chosen ?? fallback;
|
|
21
|
-
if (restored && mode !== teamOf(restored.mode)) throw new Error("Cannot change team in an existing session");
|
|
22
|
-
const team = mode === "custom" ? void 0 : applyProfile(await resolveTeam(root, mode, { library, templates }), (settings.team ?? settings.profile)?.[mode]);
|
|
23
|
-
const maxPeers = restored?.maxPeers ?? settings.workflow?.maxPeers ?? team?.maxPeers ?? 2;
|
|
24
|
-
if (!Number.isSafeInteger(maxPeers) || maxPeers < 1) throw new Error("workflow.maxPeers must be a positive integer");
|
|
25
|
-
const supervisor = !!team?.supervisor && (restored?.supervisor ?? settings.workflow?.supervisor ?? true);
|
|
26
|
-
return { mode, maxPeers, supervisor, ...team ? { team } : {} };
|
|
27
|
-
}
|
|
28
|
-
export {
|
|
29
|
-
INIT_TEAM,
|
|
30
|
-
defaultTeam,
|
|
31
|
-
teamOf2 as profileOf,
|
|
32
|
-
resolveWorkflow
|
|
33
|
-
};
|
package/templates/ALP.md
DELETED
|
@@ -1,52 +0,0 @@
|
|
|
1
|
-
# ALP project instructions
|
|
2
|
-
|
|
3
|
-
## Working relationship
|
|
4
|
-
|
|
5
|
-
The user communicates with main. The team is fixed for each session:
|
|
6
|
-
|
|
7
|
-
- Phở (default): main owns delivery and technical execution; it implements
|
|
8
|
-
directly or delegates bounded work to peer. There is no separate lead.
|
|
9
|
-
- Cafe: main owns the outcome and supervises lead; lead implements or
|
|
10
|
-
delegates to peer. Main routes implementation changes through lead.
|
|
11
|
-
|
|
12
|
-
In both, main runs by default on Opus 5.5 with high effort and full access,
|
|
13
|
-
and starts a supervisor that reviews the process after each of main's turns.
|
|
14
|
-
The supervisor asks main about mistakes; main answers and records lessons it
|
|
15
|
-
follows later.
|
|
16
|
-
|
|
17
|
-
Oracle provides read-only advice through the configured model lenses; reviewer independently
|
|
18
|
-
reviews one diff. Both return once only to the requesting coordinator. They do
|
|
19
|
-
not spawn agents. Default maximum concurrent peers is two; raise the configured
|
|
20
|
-
limit only at the user's request. Other model and effort choices belong to the
|
|
21
|
-
technical coordinator. A role grants no additional filesystem permissions,
|
|
22
|
-
credentials, or runtime tools.
|
|
23
|
-
|
|
24
|
-
## Shared work contract
|
|
25
|
-
|
|
26
|
-
- Read project instructions and the selected agent's AGENT.md. Load skills only
|
|
27
|
-
when needed; do not assume that skills or tools from another runtime exist.
|
|
28
|
-
- Each assignment states its objective, root, owned/excluded paths, constraints
|
|
29
|
-
and their sources, verification, resources, owner, and expected handoff.
|
|
30
|
-
- Keep a single writer per shared checkout. Transfer ownership before main or lead
|
|
31
|
-
intervenes in a peer's files. Parallel writers need isolated workspaces and
|
|
32
|
-
explicit shared-interface agreements.
|
|
33
|
-
- Return artifacts and real verification evidence, with remaining risks. A worker's
|
|
34
|
-
completion message is a candidate, not an independent acceptance verdict.
|
|
35
|
-
- Review should be independent of authorship: the coordinator reviews peer work;
|
|
36
|
-
in Cafe, main reviews lead work. Use reviewer for logic changes and risky
|
|
37
|
-
changes, including main-authored changes. Disclose unavailable independent review
|
|
38
|
-
rather than claiming it occurred.
|
|
39
|
-
- Changes to scope or priorities flow through the technical coordinator's current plan
|
|
40
|
-
and the affected peer's revised brief. Direct questions can receive direct answers;
|
|
41
|
-
an exceptional intervention must be reconciled with the same shared state.
|
|
42
|
-
- Tasks live in `.alp/tasks` and are shared with the user. Only the user and main
|
|
43
|
-
create or change them; other agents read them and report work they find to their
|
|
44
|
-
requester.
|
|
45
|
-
- Use only actual runtime delegation/messaging capabilities. Without them, explain
|
|
46
|
-
the limitation, perform authorized work directly where possible, and never claim
|
|
47
|
-
that a lead or peer session ran or reviewed anything.
|
|
48
|
-
|
|
49
|
-
## Project-specific constraints
|
|
50
|
-
|
|
51
|
-
Add the project's outcome, boundaries, verification commands, and authorization
|
|
52
|
-
requirements here. User instructions take precedence over this starter workflow.
|