@anhlp/alp-core 0.0.0-beta.1 → 0.0.0-beta.2

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/dist/init.js CHANGED
@@ -5,6 +5,7 @@ import path from "node:path";
5
5
  import { randomUUID } from "node:crypto";
6
6
  import { projectProfiles, profileDefaults, syncTemplateProfiles } from "./profiles.js";
7
7
  import { validateSettings } from "./validation.js";
8
+ import { INIT_TEAM } from "./workflow.js";
8
9
  async function initProject(projectRoot, { templateRoot, templates, library, syncProfiles = true, onlyManagedProfiles = false, teamIds } = {}) {
9
10
  const root = path.resolve(projectRoot);
10
11
  const sourceRoot = () => templateRoot ?? shippedTemplateRoot();
@@ -17,7 +18,7 @@ async function initProject(projectRoot, { templateRoot, templates, library, sync
17
18
  const team = await projectProfiles({ root, templates, library, teamIds });
18
19
  const files = {
19
20
  "ALP.md": await template("ALP.md"),
20
- ".alp/settings.json": JSON.stringify({ defaultAgent: "main", workflow: { mode: "pho" }, team, templateDefaults: profileDefaults(team) }, null, 2) + "\n"
21
+ ".alp/settings.json": JSON.stringify({ defaultAgent: "main", workflow: { mode: INIT_TEAM }, team, templateDefaults: profileDefaults(team) }, null, 2) + "\n"
21
22
  };
22
23
  const directories = [".alp"];
23
24
  const created = [];
package/dist/teams.d.ts CHANGED
@@ -3,20 +3,31 @@ export function validateTeam(config: any, source: any): any;
3
3
  /**
4
4
  * Where each team comes from: built in, then the library, then the project. A team
5
5
  * directory counts only with a team.json. `root` may be undefined outside a project.
6
+ * @param {string | undefined} root
7
+ * @param {{ library?: string, templates?: Record<string, string> }} [options]
6
8
  * @returns {Promise<Map<string, { source: 'builtin' | 'library' | 'project', directory?: string, overrides?: 'builtin' | 'library' }>>}
7
9
  */
8
- export function teamSources(root: any, { library, templates }?: {}): Promise<Map<string, {
10
+ export function teamSources(root: string | undefined, { library, templates }?: {
11
+ library?: string;
12
+ templates?: Record<string, string>;
13
+ }): Promise<Map<string, {
9
14
  source: "builtin" | "library" | "project";
10
15
  directory?: string;
11
16
  overrides?: "builtin" | "library";
12
17
  }>>;
13
18
  /**
14
19
  * A team by id, with its house rules.
20
+ * @param {string | undefined} root
21
+ * @param {string} id
22
+ * @param {{ library?: string, templates?: Record<string, string> }} [options]
15
23
  * @returns {Promise<{ id: string, source: 'builtin' | 'library' | 'project', label: string, description?: string, main: string,
16
24
  * members: Record<string, { role?: string, model?: string, thinking?: string }>, delegation: Record<string, string[]>,
17
25
  * oracleThinking?: Record<string, string>, oracleModels?: string[], maxPeers?: number, supervisor: false | { agent: string, model?: string, thinking?: string }, houseRules: string }>}
18
26
  */
19
- export function resolveTeam(root: any, id: any, { library, templates }?: {}): Promise<{
27
+ export function resolveTeam(root: string | undefined, id: string, { library, templates }?: {
28
+ library?: string;
29
+ templates?: Record<string, string>;
30
+ }): Promise<{
20
31
  id: string;
21
32
  source: "builtin" | "library" | "project";
22
33
  label: string;
@@ -1,14 +1,45 @@
1
1
  /**
2
- * The team a session runs in (ALPD §42): the one the caller selects, the one a resumed
3
- * session ran in, or settings' workflow.mode. Without any, the session is `custom`: its
4
- * graph is settings' delegation and no supervisor watches it.
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).
5
26
  * @returns {Promise<{ mode: string, maxPeers: number, supervisor: boolean, team?: Awaited<ReturnType<typeof resolveTeam>> }>}
6
27
  */
7
- export function resolveWorkflow(root: any, selected: any, restored: any, { library, templates }?: {}): Promise<{
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<{
8
37
  mode: string;
9
38
  maxPeers: number;
10
39
  supervisor: boolean;
11
40
  team?: Awaited<ReturnType<typeof resolveTeam>>;
12
41
  }>;
13
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";
14
45
  import { resolveTeam } from './teams.js';
package/dist/workflow.js CHANGED
@@ -1,16 +1,23 @@
1
1
  // packages/core/src/workflow.js
2
2
  import path from "node:path";
3
- import { jsonObject } from "./resolver.js";
3
+ import { jsonObject, optionalText } from "./resolver.js";
4
4
  import { applyProfile } from "./profiles.js";
5
5
  import { validateSettings } from "./validation.js";
6
6
  import { resolveTeam, teamOf } from "./teams.js";
7
7
  import { teamOf as teamOf2 } from "./teams.js";
8
- async function resolveWorkflow(root, selected, restored, { library, templates } = {}) {
9
- const settings = await jsonObject(path.join(root, ".alp/settings.json"), "INVALID_SETTINGS");
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");
10
15
  validateSettings(settings, ".alp/settings.json");
16
+ const scaffolded = scaffold && await optionalText(file) === void 0;
17
+ const fallback = defaultTeam(settings, { restored, scaffolded });
11
18
  const chosen = selected === void 0 ? void 0 : teamOf(selected);
12
- if (chosen === "custom") throw new Error("Choose a team, such as pho (Ph\u1EDF) or cafe (Cafe)");
13
- const mode = chosen ?? teamOf(restored?.mode ?? settings.workflow?.mode ?? "custom");
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;
14
21
  if (restored && mode !== teamOf(restored.mode)) throw new Error("Cannot change team in an existing session");
15
22
  const team = mode === "custom" ? void 0 : applyProfile(await resolveTeam(root, mode, { library, templates }), (settings.team ?? settings.profile)?.[mode]);
16
23
  const maxPeers = restored?.maxPeers ?? settings.workflow?.maxPeers ?? team?.maxPeers ?? 2;
@@ -19,6 +26,8 @@ async function resolveWorkflow(root, selected, restored, { library, templates }
19
26
  return { mode, maxPeers, supervisor, ...team ? { team } : {} };
20
27
  }
21
28
  export {
29
+ INIT_TEAM,
30
+ defaultTeam,
22
31
  teamOf2 as profileOf,
23
32
  resolveWorkflow
24
33
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@anhlp/alp-core",
3
- "version": "0.0.0-beta.1",
3
+ "version": "0.0.0-beta.2",
4
4
  "type": "module",
5
5
  "description": "ALP core module",
6
6
  "files": [
@@ -284,7 +284,7 @@
284
284
  "node": ">=22.12"
285
285
  },
286
286
  "dependencies": {
287
- "@anhlp/alp-protocol": "0.0.0-beta.1"
287
+ "@anhlp/alp-protocol": "0.0.0-beta.2"
288
288
  },
289
289
  "scripts": {
290
290
  "build": "node ../../scripts/build-workspace.mjs core",
package/src/init.js CHANGED
@@ -4,6 +4,7 @@ import path from 'node:path';
4
4
  import { randomUUID } from 'node:crypto';
5
5
  import { projectProfiles, profileDefaults, syncTemplateProfiles } from './profiles.js';
6
6
  import { validateSettings } from './validation.js';
7
+ import { INIT_TEAM } from './workflow.js';
7
8
 
8
9
  /**
9
10
  * Fill missing scaffold files without replacing existing user content: ALP.md and
@@ -24,7 +25,7 @@ export async function initProject(projectRoot, { templateRoot, templates, librar
24
25
  const team = await projectProfiles({ root, templates, library, teamIds });
25
26
  const files = {
26
27
  'ALP.md': await template('ALP.md'),
27
- '.alp/settings.json': JSON.stringify({ defaultAgent: 'main', workflow: { mode: 'pho' }, team, templateDefaults: profileDefaults(team) }, null, 2) + '\n',
28
+ '.alp/settings.json': JSON.stringify({ defaultAgent: 'main', workflow: { mode: INIT_TEAM }, team, templateDefaults: profileDefaults(team) }, null, 2) + '\n',
28
29
  };
29
30
  const directories = ['.alp'];
30
31
  const created = [];
package/src/teams.js CHANGED
@@ -100,6 +100,8 @@ async function directories(directory) {
100
100
  /**
101
101
  * Where each team comes from: built in, then the library, then the project. A team
102
102
  * directory counts only with a team.json. `root` may be undefined outside a project.
103
+ * @param {string | undefined} root
104
+ * @param {{ library?: string, templates?: Record<string, string> }} [options]
103
105
  * @returns {Promise<Map<string, { source: 'builtin' | 'library' | 'project', directory?: string, overrides?: 'builtin' | 'library' }>>}
104
106
  */
105
107
  export async function teamSources(root, { library, templates } = {}) {
@@ -120,6 +122,9 @@ export async function teamSources(root, { library, templates } = {}) {
120
122
 
121
123
  /**
122
124
  * A team by id, with its house rules.
125
+ * @param {string | undefined} root
126
+ * @param {string} id
127
+ * @param {{ library?: string, templates?: Record<string, string> }} [options]
123
128
  * @returns {Promise<{ id: string, source: 'builtin' | 'library' | 'project', label: string, description?: string, main: string,
124
129
  * members: Record<string, { role?: string, model?: string, thinking?: string }>, delegation: Record<string, string[]>,
125
130
  * oracleThinking?: Record<string, string>, oracleModels?: string[], maxPeers?: number, supervisor: false | { agent: string, model?: string, thinking?: string }, houseRules: string }>}
package/src/workflow.js CHANGED
@@ -1,23 +1,48 @@
1
1
  import path from 'node:path';
2
- import { jsonObject } from './resolver.js';
2
+ import { jsonObject, optionalText } from './resolver.js';
3
3
  import { applyProfile } from './profiles.js';
4
4
  import { validateSettings } from './validation.js';
5
5
  import { resolveTeam, teamOf } from './teams.js';
6
6
 
7
7
  export { teamOf as profileOf } from './teams.js';
8
8
 
9
+ /** The team `alp init` names in a new project's settings (`init.js`). */
10
+ export const INIT_TEAM = 'pho';
11
+
12
+ /**
13
+ * The team a session runs in when the caller selects none (ALPD §42, §60): the one a resumed
14
+ * session ran in, else settings' workflow.mode, else `custom` (settings' delegation graph, no
15
+ * supervisor). A project without .alp/settings.json gets init's settings when a session is
16
+ * created, so with `scaffolded` a missing file counts as naming INIT_TEAM. session.create and
17
+ * session.preview both resolve through here, so a preview names the team that would run.
18
+ * @param {Record<string, any>} settings the project's settings, `{}` when the file is missing
19
+ * @param {{ restored?: { mode: string }, scaffolded?: boolean }} [options]
20
+ */
21
+ export function defaultTeam(settings, { restored, scaffolded = false } = {}) {
22
+ return teamOf(restored?.mode ?? settings.workflow?.mode ?? (scaffolded ? INIT_TEAM : 'custom'));
23
+ }
24
+
9
25
  /**
10
- * The team a session runs in (ALPD §42): the one the caller selects, the one a resumed
11
- * session ran in, or settings' workflow.mode. Without any, the session is `custom`: its
12
- * graph is settings' delegation and no supervisor watches it.
26
+ * The team a session runs in (ALPD §42): the one the caller selects, else defaultTeam. Without
27
+ * any, the session is `custom`: its graph is settings' delegation and no supervisor watches it.
28
+ * Selecting `custom` is refused unless it is the team that would run anyway, so a client may send
29
+ * back the team session.preview named.
30
+ * @param {string} root
31
+ * @param {string | undefined} selected
32
+ * @param {{ mode: string, maxPeers?: number, supervisor?: boolean } | undefined} restored
33
+ * @param {{ library?: string, templates?: Record<string, string>, scaffold?: boolean }} [options]
34
+ * scaffold: a missing settings file will be written by init before the session runs (a preview).
13
35
  * @returns {Promise<{ mode: string, maxPeers: number, supervisor: boolean, team?: Awaited<ReturnType<typeof resolveTeam>> }>}
14
36
  */
15
- export async function resolveWorkflow(root, selected, restored, { library, templates } = {}) {
16
- const settings = await jsonObject(path.join(root, '.alp/settings.json'), 'INVALID_SETTINGS');
37
+ export async function resolveWorkflow(root, selected, restored, { library, templates, scaffold = false } = {}) {
38
+ const file = path.join(root, '.alp/settings.json');
39
+ const settings = await jsonObject(file, 'INVALID_SETTINGS');
17
40
  validateSettings(settings, '.alp/settings.json');
41
+ const scaffolded = scaffold && await optionalText(file) === undefined;
42
+ const fallback = defaultTeam(settings, { restored, scaffolded });
18
43
  const chosen = selected === undefined ? undefined : teamOf(selected);
19
- if (chosen === 'custom') throw new Error('Choose a team, such as pho (Phở) or cafe (Cafe)');
20
- const mode = chosen ?? teamOf(restored?.mode ?? settings.workflow?.mode ?? 'custom');
44
+ if (chosen === 'custom' && fallback !== 'custom') throw new Error('Choose a team, such as pho (Phở) or cafe (Cafe)');
45
+ const mode = chosen ?? fallback;
21
46
  if (restored && mode !== teamOf(restored.mode)) throw new Error('Cannot change team in an existing session');
22
47
  const team = mode === 'custom' ? undefined : applyProfile(await resolveTeam(root, mode, { library, templates }), (settings.team ?? settings.profile)?.[mode]);
23
48
  const maxPeers = restored?.maxPeers ?? settings.workflow?.maxPeers ?? team?.maxPeers ?? 2;