@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.
- package/LICENSE +202 -0
- package/NOTICE +7 -0
- package/README.md +7 -0
- package/dist/adapter.d.ts +20 -0
- package/dist/adapter.js +30 -0
- package/dist/atomic.d.ts +2 -0
- package/dist/atomic.js +28 -0
- package/dist/backup.d.ts +27 -0
- package/dist/backup.js +582 -0
- package/dist/beads.d.ts +65 -0
- package/dist/beads.js +213 -0
- package/dist/delegation.d.ts +2 -0
- package/dist/delegation.js +32 -0
- package/dist/errors.d.ts +4 -0
- package/dist/errors.js +11 -0
- package/dist/formulas.d.ts +25 -0
- package/dist/formulas.js +201 -0
- package/dist/hook-run.d.ts +44 -0
- package/dist/hook-run.js +60 -0
- package/dist/hooks.d.ts +35 -0
- package/dist/hooks.js +30 -0
- package/dist/index.d.ts +30 -0
- package/dist/index.js +63 -0
- package/dist/init.d.ts +30 -0
- package/dist/init.js +88 -0
- package/dist/library-edit.d.ts +90 -0
- package/dist/library-edit.js +315 -0
- package/dist/library.d.ts +28 -0
- package/dist/library.js +108 -0
- package/dist/log.d.ts +37 -0
- package/dist/log.js +156 -0
- package/dist/migrations.d.ts +9 -0
- package/dist/migrations.js +95 -0
- package/dist/models.d.ts +11 -0
- package/dist/models.js +44 -0
- package/dist/permissions.d.ts +14 -0
- package/dist/permissions.js +229 -0
- package/dist/profiles.d.ts +27 -0
- package/dist/profiles.js +122 -0
- package/dist/promptsafe.d.ts +1 -0
- package/dist/promptsafe.js +13 -0
- package/dist/providers.d.ts +90 -0
- package/dist/providers.js +101 -0
- package/dist/resolver.d.ts +41 -0
- package/dist/resolver.js +240 -0
- package/dist/safe-path.d.ts +2 -0
- package/dist/safe-path.js +24 -0
- package/dist/shell.d.ts +16 -0
- package/dist/shell.js +22 -0
- package/dist/state-schema.d.ts +9 -0
- package/dist/state-schema.js +54 -0
- package/dist/task-rows.d.ts +47 -0
- package/dist/task-rows.js +45 -0
- package/dist/tasks.d.ts +128 -0
- package/dist/tasks.js +835 -0
- package/dist/teams.d.ts +69 -0
- package/dist/teams.js +159 -0
- package/dist/template-path.d.ts +2 -0
- package/dist/template-path.js +10 -0
- package/dist/trust.d.ts +8 -0
- package/dist/trust.js +47 -0
- package/dist/types.d.ts +51 -0
- package/dist/upgrade.d.ts +10 -0
- package/dist/upgrade.js +200 -0
- package/dist/user-settings.d.ts +6 -0
- package/dist/user-settings.js +72 -0
- package/dist/validation.d.ts +17 -0
- package/dist/validation.js +295 -0
- package/dist/verify.d.ts +15 -0
- package/dist/verify.js +123 -0
- package/dist/workflow.d.ts +14 -0
- package/dist/workflow.js +24 -0
- package/package.json +309 -0
- package/src/adapter.d.ts +20 -0
- package/src/adapter.js +29 -0
- package/src/atomic.d.ts +2 -0
- package/src/atomic.js +28 -0
- package/src/backup.d.ts +27 -0
- package/src/backup.js +570 -0
- package/src/beads.js +236 -0
- package/src/delegation.js +28 -0
- package/src/errors.js +7 -0
- package/src/formulas.d.ts +25 -0
- package/src/formulas.js +202 -0
- package/src/hook-run.js +57 -0
- package/src/hooks.js +36 -0
- package/src/index.js +31 -0
- package/src/init.js +92 -0
- package/src/library-edit.js +346 -0
- package/src/library.js +117 -0
- package/src/log.d.ts +37 -0
- package/src/log.js +177 -0
- package/src/migrations.d.ts +9 -0
- package/src/migrations.js +111 -0
- package/src/models.js +45 -0
- package/src/permissions.d.ts +14 -0
- package/src/permissions.js +242 -0
- package/src/profiles.js +139 -0
- package/src/promptsafe.d.ts +1 -0
- package/src/promptsafe.js +19 -0
- package/src/providers.js +122 -0
- package/src/resolver.js +238 -0
- package/src/safe-path.js +20 -0
- package/src/shell.js +28 -0
- package/src/state-schema.d.ts +9 -0
- package/src/state-schema.js +60 -0
- package/src/task-rows.js +44 -0
- package/src/tasks.d.ts +128 -0
- package/src/tasks.js +881 -0
- package/src/teams.js +170 -0
- package/src/template-path.js +8 -0
- package/src/trust.js +59 -0
- package/src/types.d.ts +51 -0
- package/src/upgrade.js +195 -0
- package/src/user-settings.d.ts +6 -0
- package/src/user-settings.js +76 -0
- package/src/validation.d.ts +17 -0
- package/src/validation.js +302 -0
- package/src/verify.d.ts +15 -0
- package/src/verify.js +127 -0
- package/src/workflow.js +28 -0
- package/templates/ALP.md +52 -0
- package/templates/agents/lead/AGENT.md +90 -0
- package/templates/agents/main/AGENT.md +150 -0
- package/templates/agents/oracle/AGENT.md +44 -0
- package/templates/agents/peer/AGENT.md +55 -0
- package/templates/agents/reviewer/AGENT.md +80 -0
- package/templates/agents/supervisor/AGENT.md +72 -0
- package/templates/providers/antigravity.json +6 -0
- package/templates/providers/opencode.json +7 -0
- package/templates/role-skills.json +8 -0
- package/templates/skills/bug-loop/SKILL.md +44 -0
- package/templates/skills/bug-loop/references/test-proof.md +25 -0
- package/templates/skills/goal-griller/SKILL.md +49 -0
- package/templates/skills/prompt-leverage/SKILL.md +37 -0
- package/templates/skills/prompt-leverage/references/brief-template.md +27 -0
- package/templates/skills/sequence-execution-plan/SKILL.md +42 -0
- package/templates/skills/sequence-execution-plan/references/plan-template.md +22 -0
- package/templates/skills/smart-commits/SKILL.md +40 -0
- package/templates/skills/xia/SKILL.md +42 -0
- package/templates/teams/cafe/HOUSE_RULES.md +1 -0
- package/templates/teams/cafe/team.json +15 -0
- package/templates/teams/pho/HOUSE_RULES.md +1 -0
- package/templates/teams/pho/team.json +14 -0
package/dist/library.js
ADDED
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
// packages/core/src/library.js
|
|
2
|
+
import { shippedTemplateRoot } from "./template-path.js";
|
|
3
|
+
import { mkdir, readFile, readdir, writeFile } from "node:fs/promises";
|
|
4
|
+
import { moveFile } from "./atomic.js";
|
|
5
|
+
import { createHash, randomUUID } from "node:crypto";
|
|
6
|
+
import os from "node:os";
|
|
7
|
+
import path from "node:path";
|
|
8
|
+
import { AlpError } from "./errors.js";
|
|
9
|
+
function alpHome(env = process.env) {
|
|
10
|
+
return path.resolve(env.ALP_HOME || path.join(os.homedir(), ".alp"));
|
|
11
|
+
}
|
|
12
|
+
var MANIFEST = "library.json";
|
|
13
|
+
var sha = (text) => createHash("sha256").update(text.replaceAll("\r\n", "\n")).digest("hex");
|
|
14
|
+
async function optional(file) {
|
|
15
|
+
try {
|
|
16
|
+
return await readFile(file, "utf8");
|
|
17
|
+
} catch (error) {
|
|
18
|
+
if (error.code === "ENOENT") return void 0;
|
|
19
|
+
throw error;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
async function shippedFiles({ templateRoot, templates }) {
|
|
23
|
+
if (templates) return Object.fromEntries(Object.entries(templates).filter(([name]) => name === "role-skills.json" || name.startsWith("skills/") || name.startsWith("providers/")));
|
|
24
|
+
const root = templateRoot ?? shippedTemplateRoot();
|
|
25
|
+
const files = { "role-skills.json": await readFile(new URL("role-skills.json", root), "utf8") };
|
|
26
|
+
async function walk(relative) {
|
|
27
|
+
for (const entry of await readdir(new URL(`${relative}/`, root), { withFileTypes: true })) {
|
|
28
|
+
if (entry.isDirectory()) await walk(`${relative}/${entry.name}`);
|
|
29
|
+
else if (entry.isFile()) files[`${relative}/${entry.name}`] = await readFile(new URL(`${relative}/${entry.name}`, root), "utf8");
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
await walk("skills");
|
|
33
|
+
await walk("providers");
|
|
34
|
+
return files;
|
|
35
|
+
}
|
|
36
|
+
async function seedLibrary(home, options = {}) {
|
|
37
|
+
const shipped = await shippedFiles(options);
|
|
38
|
+
await mkdir(home, { recursive: true, mode: 448 });
|
|
39
|
+
const manifestPath = path.join(home, MANIFEST);
|
|
40
|
+
const manifestText = await optional(manifestPath);
|
|
41
|
+
const manifest = manifestText ? JSON.parse(manifestText) : { files: {} };
|
|
42
|
+
const created = [];
|
|
43
|
+
const updated = [];
|
|
44
|
+
for (const [name, content] of Object.entries(shipped).sort(([a], [b]) => a.localeCompare(b))) {
|
|
45
|
+
const target = path.join(home, ...name.split("/"));
|
|
46
|
+
const current = await optional(target);
|
|
47
|
+
const seeded = manifest.files[name];
|
|
48
|
+
if (seeded === void 0) {
|
|
49
|
+
if (current !== void 0) continue;
|
|
50
|
+
await mkdir(path.dirname(target), { recursive: true });
|
|
51
|
+
await writeFile(target, content, { flag: "wx" }).catch((error) => {
|
|
52
|
+
if (error.code !== "EEXIST") throw error;
|
|
53
|
+
});
|
|
54
|
+
manifest.files[name] = sha(content);
|
|
55
|
+
created.push(name);
|
|
56
|
+
} else if (current !== void 0 && sha(current) === seeded && seeded !== sha(content)) {
|
|
57
|
+
await writeFile(target, content);
|
|
58
|
+
manifest.files[name] = sha(content);
|
|
59
|
+
updated.push(name);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
if (created.length || updated.length || manifestText === void 0) {
|
|
63
|
+
await mkdir(home, { recursive: true });
|
|
64
|
+
const temporary = `${manifestPath}.${randomUUID().slice(0, 8)}.tmp`;
|
|
65
|
+
await writeFile(temporary, JSON.stringify(manifest, null, 2) + "\n");
|
|
66
|
+
await moveFile(temporary, manifestPath);
|
|
67
|
+
}
|
|
68
|
+
return { created, updated };
|
|
69
|
+
}
|
|
70
|
+
var seeding = /* @__PURE__ */ new Map();
|
|
71
|
+
function ensureLibrary(home, options = {}) {
|
|
72
|
+
let done = seeding.get(home);
|
|
73
|
+
if (!done) {
|
|
74
|
+
done = seedLibrary(home, options).catch((error) => {
|
|
75
|
+
seeding.delete(home);
|
|
76
|
+
throw error;
|
|
77
|
+
});
|
|
78
|
+
seeding.set(home, done);
|
|
79
|
+
}
|
|
80
|
+
return done;
|
|
81
|
+
}
|
|
82
|
+
var skillName = (name) => typeof name === "string" && /^[\w.-]+$/.test(name) && name !== "." && name !== "..";
|
|
83
|
+
async function librarySkills(home, agent) {
|
|
84
|
+
const file = path.join(home, "role-skills.json");
|
|
85
|
+
const text = await optional(file);
|
|
86
|
+
if (text === void 0) return [];
|
|
87
|
+
let roles;
|
|
88
|
+
try {
|
|
89
|
+
roles = JSON.parse(text);
|
|
90
|
+
} catch (cause) {
|
|
91
|
+
throw new AlpError("INVALID_LIBRARY", `${file}: ${cause.message}`, { cause });
|
|
92
|
+
}
|
|
93
|
+
if (!roles || typeof roles !== "object" || Array.isArray(roles)) throw new AlpError("INVALID_LIBRARY", `${file}: expected an object of role names to skill lists`);
|
|
94
|
+
const names = roles[agent] ?? [];
|
|
95
|
+
if (!Array.isArray(names) || !names.every(skillName)) throw new AlpError("INVALID_LIBRARY", `${file}: ${agent} must list skill directory names`);
|
|
96
|
+
const skills = [];
|
|
97
|
+
for (const name of new Set(names)) {
|
|
98
|
+
const skillPath = path.join(home, "skills", name, "SKILL.md");
|
|
99
|
+
if (await optional(skillPath) !== void 0) skills.push({ name, path: skillPath });
|
|
100
|
+
}
|
|
101
|
+
return skills;
|
|
102
|
+
}
|
|
103
|
+
export {
|
|
104
|
+
alpHome,
|
|
105
|
+
ensureLibrary,
|
|
106
|
+
librarySkills,
|
|
107
|
+
seedLibrary
|
|
108
|
+
};
|
package/dist/log.d.ts
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
export type LogLevel = 'debug' | 'info' | 'warn' | 'error';
|
|
2
|
+
export const LEVELS: Readonly<Record<LogLevel, number>>;
|
|
3
|
+
export const REDACTED: string;
|
|
4
|
+
export const OMITTED: string;
|
|
5
|
+
export type LogFields = {
|
|
6
|
+
code?: string;
|
|
7
|
+
error?: unknown;
|
|
8
|
+
sessionId?: string;
|
|
9
|
+
rootId?: string;
|
|
10
|
+
assignmentId?: string;
|
|
11
|
+
clientMessageId?: string;
|
|
12
|
+
requestId?: string;
|
|
13
|
+
agent?: string;
|
|
14
|
+
[key: string]: unknown;
|
|
15
|
+
};
|
|
16
|
+
export type LogRecord = {
|
|
17
|
+
time: string;
|
|
18
|
+
level: LogLevel;
|
|
19
|
+
component: string;
|
|
20
|
+
code?: string;
|
|
21
|
+
msg: string;
|
|
22
|
+
error?: { name?: string; code?: string | number; message: string; stack?: string; cause?: unknown };
|
|
23
|
+
[key: string]: unknown;
|
|
24
|
+
};
|
|
25
|
+
export type Logger = {
|
|
26
|
+
component: string;
|
|
27
|
+
debug(message: string, fields?: LogFields): void;
|
|
28
|
+
info(message: string, fields?: LogFields): void;
|
|
29
|
+
warn(message: string, fields?: LogFields): void;
|
|
30
|
+
error(message: string, fields?: LogFields): void;
|
|
31
|
+
child(extra?: LogFields, component?: string): Logger;
|
|
32
|
+
};
|
|
33
|
+
export function scrubText(text: unknown, options?: { env?: Record<string, string | undefined>; max?: number }): string;
|
|
34
|
+
export function describeError(error: unknown, options?: { env?: Record<string, string | undefined> }): { name?: string; code?: string | number; message: string; stack?: string; cause?: unknown };
|
|
35
|
+
export function redact(value: unknown, options?: { env?: Record<string, string | undefined> }): any;
|
|
36
|
+
export function createLogger(options: { component: string; level?: LogLevel; context?: LogFields; write?: (line: string) => void; now?: () => Date; env?: Record<string, string | undefined> }): Logger;
|
|
37
|
+
export function readLogTail(file: string, options?: { bytes?: number; env?: Record<string, string | undefined> }): Promise<Array<LogRecord | { legacy: string }>>;
|
package/dist/log.js
ADDED
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
// packages/core/src/log.js
|
|
2
|
+
import { open, stat } from "node:fs/promises";
|
|
3
|
+
var LEVELS = Object.freeze({ debug: 10, info: 20, warn: 30, error: 40 });
|
|
4
|
+
var REDACTED = "[redacted]";
|
|
5
|
+
var OMITTED = "[omitted]";
|
|
6
|
+
var MAX_STRING = 2e3;
|
|
7
|
+
var MAX_DEPTH = 5;
|
|
8
|
+
var MAX_KEYS = 50;
|
|
9
|
+
var MAX_ITEMS = 20;
|
|
10
|
+
var STACK_LINES = 8;
|
|
11
|
+
var SECRET_KEY = /pass(word|wd)?|secret|token|api[-_]?key|authorization|credential|cookie|private[-_]?key|bearer|signature/i;
|
|
12
|
+
var OMITTED_KEY = /^(env|environment|prompt|prompts|instructions|content|text|input|headers|body|args|argv)$/i;
|
|
13
|
+
var SECRET_NAME = /KEY|TOKEN|SECRET|PASSWORD|PASSWD|CREDENTIAL|AUTH|COOKIE|PRIVATE/i;
|
|
14
|
+
var PATTERNS = [
|
|
15
|
+
[/\b(Bearer|Basic)\s+[A-Za-z0-9._~+/=-]{8,}/gi, `$1 ${REDACTED}`],
|
|
16
|
+
[/\b(sk|rk|pk)-[A-Za-z0-9_-]{16,}/g, REDACTED],
|
|
17
|
+
[/\bgh[pousr]_[A-Za-z0-9]{20,}/g, REDACTED],
|
|
18
|
+
[/\bgithub_pat_[A-Za-z0-9_]{20,}/g, REDACTED],
|
|
19
|
+
[/\bxox[abprs]-[A-Za-z0-9-]{10,}/g, REDACTED],
|
|
20
|
+
[/\bAKIA[0-9A-Z]{16}\b/g, REDACTED],
|
|
21
|
+
[/\bAIza[0-9A-Za-z_-]{30,}/g, REDACTED],
|
|
22
|
+
[/\beyJ[A-Za-z0-9_-]{8,}\.eyJ[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{4,}/g, REDACTED],
|
|
23
|
+
[/-----BEGIN [A-Z ]*PRIVATE KEY-----[\s\S]*?(-----END [A-Z ]*PRIVATE KEY-----|$)/g, REDACTED],
|
|
24
|
+
[/(\b[a-z][a-z0-9+.-]*:\/\/)[^\s/@:]+:[^\s/@]+@/gi, `$1${REDACTED}@`],
|
|
25
|
+
// NAME=value and NAME: value for names that look like secrets; also JSON "token": "value".
|
|
26
|
+
[/(\b[\w.-]*(?:key|token|secret|passw(?:or)?d|credential|authorization|cookie)[\w.-]*["']?\s*[=:]\s*)(?:"[^"]{4,}"|'[^']{4,}'|[^\s,;&"']{8,})/gi, `$1${REDACTED}`],
|
|
27
|
+
// --token value, --api-key value
|
|
28
|
+
[/(--[\w-]*(?:key|token|secret|passw(?:or)?d|credential)[\w-]*\s+)[^\s-][^\s]*/gi, `$1${REDACTED}`]
|
|
29
|
+
];
|
|
30
|
+
function secretValues(env) {
|
|
31
|
+
const values = /* @__PURE__ */ new Set();
|
|
32
|
+
for (const [name, value] of Object.entries(env ?? {})) if (typeof value === "string" && value.length >= 8 && SECRET_NAME.test(name)) values.add(value);
|
|
33
|
+
return [...values].sort((a, b) => b.length - a.length);
|
|
34
|
+
}
|
|
35
|
+
function scrubText(text, { env = process.env, max = MAX_STRING } = {}) {
|
|
36
|
+
let result = String(text);
|
|
37
|
+
for (const value of secretValues(env)) result = result.split(value).join(REDACTED);
|
|
38
|
+
for (const [pattern, replacement] of PATTERNS) result = result.replace(pattern, replacement);
|
|
39
|
+
return result.length > max ? `${result.slice(0, max)}... (${result.length - max} more characters)` : result;
|
|
40
|
+
}
|
|
41
|
+
function describeError(error, options = {}) {
|
|
42
|
+
if (!(error instanceof Error)) return { message: scrubText(error, options) };
|
|
43
|
+
const stack = typeof error.stack === "string" ? error.stack.split("\n").slice(0, STACK_LINES).join("\n") : void 0;
|
|
44
|
+
return {
|
|
45
|
+
name: error.name,
|
|
46
|
+
...typeof error.code === "string" || typeof error.code === "number" ? { code: error.code } : {},
|
|
47
|
+
message: scrubText(error.message, options),
|
|
48
|
+
...stack ? { stack: scrubText(stack, options) } : {},
|
|
49
|
+
...error.cause !== void 0 ? { cause: describeError(error.cause, options) } : {}
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
function redact(value, options = {}, depth = 0, seen = /* @__PURE__ */ new WeakSet()) {
|
|
53
|
+
if (value === null || value === void 0 || typeof value === "boolean" || typeof value === "number") return value;
|
|
54
|
+
if (typeof value === "string") return scrubText(value, options);
|
|
55
|
+
if (typeof value === "bigint") return String(value);
|
|
56
|
+
if (typeof value !== "object") return void 0;
|
|
57
|
+
if (value instanceof Error) return describeError(value, options);
|
|
58
|
+
if (seen.has(value)) return "[circular]";
|
|
59
|
+
if (depth >= MAX_DEPTH) return "[too deep]";
|
|
60
|
+
seen.add(value);
|
|
61
|
+
if (Array.isArray(value)) {
|
|
62
|
+
const items = value.slice(0, MAX_ITEMS).map((item) => redact(item, options, depth + 1, seen));
|
|
63
|
+
return value.length > MAX_ITEMS ? [...items, `... ${value.length - MAX_ITEMS} more`] : items;
|
|
64
|
+
}
|
|
65
|
+
const result = {};
|
|
66
|
+
for (const [key, item] of Object.entries(value).slice(0, MAX_KEYS)) {
|
|
67
|
+
result[key] = OMITTED_KEY.test(key) ? OMITTED : SECRET_KEY.test(key) ? REDACTED : redact(item, options, depth + 1, seen);
|
|
68
|
+
}
|
|
69
|
+
return result;
|
|
70
|
+
}
|
|
71
|
+
var CORRELATION = ["sessionId", "rootId", "assignmentId", "clientMessageId", "requestId", "agent"];
|
|
72
|
+
var stderrSink = (line) => {
|
|
73
|
+
try {
|
|
74
|
+
process.stderr.write(`${line}
|
|
75
|
+
`);
|
|
76
|
+
} catch {
|
|
77
|
+
}
|
|
78
|
+
};
|
|
79
|
+
function createLogger({ component, level, context = {}, write = stderrSink, now = () => /* @__PURE__ */ new Date(), env = process.env } = {}) {
|
|
80
|
+
const threshold = LEVELS[level ?? env.ALP_LOG_LEVEL] ?? LEVELS.info;
|
|
81
|
+
const options = { env };
|
|
82
|
+
const emit = (name, message, fields = {}) => {
|
|
83
|
+
if (LEVELS[name] < threshold) return;
|
|
84
|
+
try {
|
|
85
|
+
const { code, error, ...rest } = { ...context, ...fields };
|
|
86
|
+
const record = {
|
|
87
|
+
time: now().toISOString(),
|
|
88
|
+
level: name,
|
|
89
|
+
component,
|
|
90
|
+
...code ? { code: String(code) } : {},
|
|
91
|
+
msg: scrubText(message, options)
|
|
92
|
+
};
|
|
93
|
+
for (const key of CORRELATION) if (rest[key] !== void 0) record[key] = redact(String(rest[key]), options);
|
|
94
|
+
if (error !== void 0) {
|
|
95
|
+
record.error = describeError(error, options);
|
|
96
|
+
if (!record.code && record.error.code !== void 0) record.code = String(record.error.code);
|
|
97
|
+
}
|
|
98
|
+
for (const [key, value] of Object.entries(rest)) {
|
|
99
|
+
if (CORRELATION.includes(key)) continue;
|
|
100
|
+
record[key] = OMITTED_KEY.test(key) ? OMITTED : SECRET_KEY.test(key) ? REDACTED : redact(value, options);
|
|
101
|
+
}
|
|
102
|
+
write(JSON.stringify(record));
|
|
103
|
+
} catch {
|
|
104
|
+
}
|
|
105
|
+
};
|
|
106
|
+
return {
|
|
107
|
+
component,
|
|
108
|
+
debug: (message, fields) => emit("debug", message, fields),
|
|
109
|
+
info: (message, fields) => emit("info", message, fields),
|
|
110
|
+
warn: (message, fields) => emit("warn", message, fields),
|
|
111
|
+
error: (message, fields) => emit("error", message, fields),
|
|
112
|
+
child: (extra = {}, name = component) => createLogger({ component: name, level, context: { ...context, ...extra }, write, now, env })
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
var TAIL_BYTES = 256 * 1024;
|
|
116
|
+
async function readLogTail(file, { bytes = TAIL_BYTES, env = process.env } = {}) {
|
|
117
|
+
let handle;
|
|
118
|
+
try {
|
|
119
|
+
handle = await open(file, "r");
|
|
120
|
+
const { size } = await stat(file);
|
|
121
|
+
const start = Math.max(0, size - bytes);
|
|
122
|
+
const buffer = Buffer.alloc(size - start);
|
|
123
|
+
await handle.read(buffer, 0, buffer.length, start);
|
|
124
|
+
const lines = buffer.toString("utf8").split("\n");
|
|
125
|
+
if (start > 0) lines.shift();
|
|
126
|
+
const records = [];
|
|
127
|
+
for (const line of lines) {
|
|
128
|
+
if (!line.trim()) continue;
|
|
129
|
+
try {
|
|
130
|
+
const record = JSON.parse(line);
|
|
131
|
+
if (record && typeof record === "object" && typeof record.time === "string" && typeof record.level === "string" && typeof record.msg === "string") {
|
|
132
|
+
records.push(redact(record, { env }));
|
|
133
|
+
continue;
|
|
134
|
+
}
|
|
135
|
+
} catch {
|
|
136
|
+
}
|
|
137
|
+
records.push({ legacy: scrubText(line, { env, max: 500 }) });
|
|
138
|
+
}
|
|
139
|
+
return records;
|
|
140
|
+
} catch (error) {
|
|
141
|
+
if (error?.code === "ENOENT") return [];
|
|
142
|
+
throw error;
|
|
143
|
+
} finally {
|
|
144
|
+
await handle?.close();
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
export {
|
|
148
|
+
LEVELS,
|
|
149
|
+
OMITTED,
|
|
150
|
+
REDACTED,
|
|
151
|
+
createLogger,
|
|
152
|
+
describeError,
|
|
153
|
+
readLogTail,
|
|
154
|
+
redact,
|
|
155
|
+
scrubText
|
|
156
|
+
};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export type Migration = { from: number; to: number; name: string; run(home: string): Promise<void> };
|
|
2
|
+
export const MIGRATIONS: Migration[];
|
|
3
|
+
export type MigrationResult = { from: number; to: number; steps: Array<{ name: string; from: number; to: number; backup: string; resumed: boolean }> };
|
|
4
|
+
export function migrateState(home: string, options?: {
|
|
5
|
+
by?: string;
|
|
6
|
+
migrations?: Migration[];
|
|
7
|
+
target?: number;
|
|
8
|
+
log?: { info(message: string, fields?: object): void; warn(message: string, fields?: object): void };
|
|
9
|
+
}): Promise<MigrationResult>;
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
// packages/core/src/migrations.js
|
|
2
|
+
import { readdir, readFile, rm, stat } from "node:fs/promises";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { AlpError } from "./errors.js";
|
|
5
|
+
import { writeAtomic } from "./atomic.js";
|
|
6
|
+
import { STATE_SCHEMA, assertStateSupported, migrationFile, readStateSchema, stateDir, writeStateSchema } from "./state-schema.js";
|
|
7
|
+
import { STATE_UNITS, automaticBackup, restoreBackup } from "./backup.js";
|
|
8
|
+
async function timelineCursors(home) {
|
|
9
|
+
const directory = path.join(stateDir(home), "timeline");
|
|
10
|
+
const names = await readdir(directory).catch((error) => {
|
|
11
|
+
if (error?.code === "ENOENT") return [];
|
|
12
|
+
throw error;
|
|
13
|
+
});
|
|
14
|
+
for (const name of names.filter((name2) => name2.endsWith(".jsonl"))) {
|
|
15
|
+
const file = path.join(directory, name);
|
|
16
|
+
const lines = [];
|
|
17
|
+
let cursor = 0;
|
|
18
|
+
let changed = false;
|
|
19
|
+
for (const line of (await readFile(file, "utf8")).split("\n")) {
|
|
20
|
+
if (!line) continue;
|
|
21
|
+
let envelope;
|
|
22
|
+
try {
|
|
23
|
+
envelope = JSON.parse(line);
|
|
24
|
+
} catch {
|
|
25
|
+
lines.push(line);
|
|
26
|
+
continue;
|
|
27
|
+
}
|
|
28
|
+
cursor = typeof envelope?.cursor === "number" && envelope.cursor > cursor ? envelope.cursor : cursor + 1;
|
|
29
|
+
if (envelope?.cursor === cursor) {
|
|
30
|
+
lines.push(line);
|
|
31
|
+
continue;
|
|
32
|
+
}
|
|
33
|
+
lines.push(JSON.stringify({ ...envelope, cursor }));
|
|
34
|
+
changed = true;
|
|
35
|
+
}
|
|
36
|
+
if (changed) await writeAtomic(file, lines.map((line) => `${line}
|
|
37
|
+
`).join(""));
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
var MIGRATIONS = [
|
|
41
|
+
{ from: 1, to: 2, name: "timeline-cursors", run: timelineCursors }
|
|
42
|
+
];
|
|
43
|
+
async function readJournal(home) {
|
|
44
|
+
try {
|
|
45
|
+
return JSON.parse(await readFile(migrationFile(home), "utf8"));
|
|
46
|
+
} catch (error) {
|
|
47
|
+
if (error?.code === "ENOENT") return void 0;
|
|
48
|
+
return {};
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
var exists = (file) => stat(file).then(() => true, () => false);
|
|
52
|
+
async function migrateState(home, { by = "alp", migrations = MIGRATIONS, target = STATE_SCHEMA, log } = {}) {
|
|
53
|
+
const info = assertStateSupported(await readStateSchema(home), home);
|
|
54
|
+
let journal = await readJournal(home);
|
|
55
|
+
if (journal && Number.isSafeInteger(journal.to) && journal.to <= info.schema) {
|
|
56
|
+
await rm(migrationFile(home), { force: true });
|
|
57
|
+
journal = void 0;
|
|
58
|
+
}
|
|
59
|
+
if (info.fresh && !journal) {
|
|
60
|
+
await writeStateSchema(home, target, by);
|
|
61
|
+
return { from: target, to: target, steps: [] };
|
|
62
|
+
}
|
|
63
|
+
let schema = info.schema;
|
|
64
|
+
const steps = [];
|
|
65
|
+
while (schema < target) {
|
|
66
|
+
const step = migrations.find((candidate) => candidate.from === schema);
|
|
67
|
+
if (!step) throw new AlpError("STATE_MIGRATION_MISSING", `ALP has no migration from state schema ${schema}; the state was not changed`);
|
|
68
|
+
const resumed = journal?.to === step.to && typeof journal.backup === "string" && await exists(journal.backup);
|
|
69
|
+
const backup = resumed ? journal.backup : await automaticBackup(home, `pre-migrate-v${step.from}-v${step.to}`, { units: STATE_UNITS, by });
|
|
70
|
+
if (!resumed) await writeAtomic(migrationFile(home), JSON.stringify({ from: step.from, to: step.to, name: step.name, backup, by, startedAt: (/* @__PURE__ */ new Date()).toISOString() }) + "\n");
|
|
71
|
+
log?.info(`${resumed ? "continuing" : "running"} state migration ${step.name} (schema ${step.from} \u2192 ${step.to}); backup ${backup}`, { code: "ALP_STATE_MIGRATING", from: step.from, to: step.to, migration: step.name, resumed });
|
|
72
|
+
try {
|
|
73
|
+
await step.run(home);
|
|
74
|
+
} catch (error) {
|
|
75
|
+
try {
|
|
76
|
+
await restoreBackup({ home, file: backup, projects: false, safety: false, clearLive: false, by });
|
|
77
|
+
} catch (rollback) {
|
|
78
|
+
throw new AlpError("STATE_MIGRATION_ROLLBACK_FAILED", `state migration ${step.name} failed (${error?.message ?? error}) and putting the state back from ${backup} failed too (${rollback?.message ?? rollback}); the next start tries the migration again. See docs/alp/data-recovery.md`, { cause: error });
|
|
79
|
+
}
|
|
80
|
+
await rm(migrationFile(home), { force: true });
|
|
81
|
+
throw new AlpError("STATE_MIGRATION_FAILED", `state migration ${step.name} (schema ${step.from} \u2192 ${step.to}) failed: ${error?.message ?? error}; the state was put back from ${backup} and is unchanged. See docs/alp/data-recovery.md`, { cause: error });
|
|
82
|
+
}
|
|
83
|
+
await writeStateSchema(home, step.to, by);
|
|
84
|
+
await rm(migrationFile(home), { force: true });
|
|
85
|
+
steps.push({ name: step.name, from: step.from, to: step.to, backup, resumed });
|
|
86
|
+
schema = step.to;
|
|
87
|
+
journal = void 0;
|
|
88
|
+
}
|
|
89
|
+
if (!info.marked && !steps.length) await writeStateSchema(home, schema, by);
|
|
90
|
+
return { from: info.schema, to: schema, steps };
|
|
91
|
+
}
|
|
92
|
+
export {
|
|
93
|
+
MIGRATIONS,
|
|
94
|
+
migrateState
|
|
95
|
+
};
|
package/dist/models.d.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @type {Array<{ runtime: 'codex' | 'claude' | 'antigravity', id: string, label: string, description: string, efforts: string[], effort?: string }>}
|
|
3
|
+
*/
|
|
4
|
+
export const MODELS: Array<{
|
|
5
|
+
runtime: "codex" | "claude" | "antigravity";
|
|
6
|
+
id: string;
|
|
7
|
+
label: string;
|
|
8
|
+
description: string;
|
|
9
|
+
efforts: string[];
|
|
10
|
+
effort?: string;
|
|
11
|
+
}>;
|
package/dist/models.js
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
// packages/core/src/models.js
|
|
2
|
+
var codexFull = ["low", "medium", "high", "xhigh", "max", "ultra"];
|
|
3
|
+
var codexStandard = ["low", "medium", "high", "xhigh", "max"];
|
|
4
|
+
var claudeFull = ["low", "medium", "high", "xhigh", "max", "ultracode"];
|
|
5
|
+
var claudeLegacy = ["off", "low", "medium", "high", "xhigh", "max", "ultracode"];
|
|
6
|
+
var claude46 = ["off", "low", "medium", "high", "max"];
|
|
7
|
+
var MODELS = [
|
|
8
|
+
["codex", "gpt-6.1-sol", "GPT-6.1-Sol", "Latest workhorse model for coding and everyday work.", codexFull, "low"],
|
|
9
|
+
// ALP runs GPT-6 Astra at low effort only.
|
|
10
|
+
["codex", "gpt-6-astra", "GPT-6-Astra", "Frontier intelligence for the most demanding work.", ["low"], "low"],
|
|
11
|
+
["codex", "gpt-6-sol", "GPT-6-Sol", "Previous generation workhorse model.", codexFull, "low"],
|
|
12
|
+
["codex", "gpt-6-luna", "GPT-6-Luna", "Fast and affordable model for easier tasks.", codexStandard, "low"],
|
|
13
|
+
["codex", "gpt-5.6-sol", "GPT-5.6-Sol", "Older generation workhorse model.", codexFull, "low"],
|
|
14
|
+
["codex", "gpt-5.6-terra", "GPT-5.6-Terra", "Older balanced model for straightforward work.", codexFull, "low"],
|
|
15
|
+
["codex", "gpt-5.6-luna", "GPT-5.6-Luna", "Older fast and efficient model.", codexStandard, "low"],
|
|
16
|
+
["claude", "claude-opus-5-5", "Opus 5.5", "Latest release.", claudeFull, "medium"],
|
|
17
|
+
["claude", "claude-opus-5", "Opus 5", "Previous release.", claudeLegacy, "high"],
|
|
18
|
+
["claude", "claude-fable-5-1", "Fable 5.1", "Most powerful model.", claudeFull, "high"],
|
|
19
|
+
["claude", "claude-fable-5", "Fable 5", "Previous release.", claudeFull, "high"],
|
|
20
|
+
["claude", "claude-opus-4-8[1m]", "Opus 4.8 1M", "Opus 4.8 with 1M context window.", claudeLegacy, "high"],
|
|
21
|
+
["claude", "claude-opus-4-8", "Opus 4.8", "Previous release.", claudeLegacy, "high"],
|
|
22
|
+
["claude", "claude-sonnet-5-5", "Sonnet 5.5", "Best for everyday tasks.", claudeFull, "medium"],
|
|
23
|
+
["claude", "claude-sonnet-5", "Sonnet 5", "Previous release.", claudeLegacy, "high"],
|
|
24
|
+
["claude", "claude-sonnet-5[1m]", "Sonnet 5 1M", "Sonnet 5 with 1M context window.", claudeLegacy, "high"],
|
|
25
|
+
["claude", "claude-opus-4-7[1m]", "Opus 4.7 1M", "Opus 4.7 with 1M context window.", claudeLegacy, "high"],
|
|
26
|
+
["claude", "claude-opus-4-7", "Opus 4.7", "Previous release.", claudeLegacy, "high"],
|
|
27
|
+
["claude", "claude-opus-4-6[1m]", "Opus 4.6 1M", "Opus 4.6 with 1M context window.", claude46, "high"],
|
|
28
|
+
["claude", "claude-opus-4-6", "Opus 4.6", "Most capable for complex work.", claude46, "high"],
|
|
29
|
+
["claude", "claude-sonnet-4-6[1m]", "Sonnet 4.6 1M", "Sonnet 4.6 with 1M context window.", claude46, "high"],
|
|
30
|
+
["claude", "claude-sonnet-4-6", "Sonnet 4.6", "Best for everyday tasks.", claude46, "high"],
|
|
31
|
+
["claude", "claude-haiku-4-5", "Haiku 4.5", "Fastest for quick answers.", [], void 0],
|
|
32
|
+
["claude", "sonnet", "sonnet", "From Claude settings.json model.", [], void 0],
|
|
33
|
+
["antigravity", "gemini-3.8-flash-high", "Gemini 3.8 Flash (High)", "High reasoning effort workhorse for complex tasks.", [], void 0],
|
|
34
|
+
["antigravity", "gemini-3.8-flash-medium", "Gemini 3.8 Flash (Medium)", "Balanced speed and reasoning.", [], void 0],
|
|
35
|
+
["antigravity", "gemini-3.8-flash-low", "Gemini 3.8 Flash (Low)", "Fast and efficient for straightforward work.", [], void 0],
|
|
36
|
+
["antigravity", "gemini-2.5-pro", "Gemini 2.5 Pro", "Deep reasoning for coding and analysis.", [], void 0],
|
|
37
|
+
["antigravity", "gemini-2.5-flash", "Gemini 2.5 Flash", "Fast and versatile everyday model.", [], void 0],
|
|
38
|
+
["antigravity", "claude-sonnet-4-6", "Claude Sonnet 4.6", "Sonnet 4.6 via Antigravity.", [], void 0],
|
|
39
|
+
["antigravity", "claude-opus-4-6-thinking", "Claude Opus 4.6 Thinking", "Opus 4.6 with thinking via Antigravity.", [], void 0],
|
|
40
|
+
["antigravity", "gpt-oss-120b-medium", "GPT-OSS 120B (Medium)", "Open weights model via Antigravity.", [], void 0]
|
|
41
|
+
].map(([runtime, id, label, description, efforts, effort]) => ({ runtime, id, label, description, efforts, ...effort ? { effort } : {} }));
|
|
42
|
+
export {
|
|
43
|
+
MODELS
|
|
44
|
+
};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export type PermissionBase = 'read-only' | 'workspace-write' | 'full-access';
|
|
2
|
+
export type PermissionProfile = { name: string; base: PermissionBase; allow: string[]; ask: string[]; deny: string[]; beyondMode: 'refuse' | 'ask'; workdir?: 'copy' };
|
|
3
|
+
export const BASES: PermissionBase[];
|
|
4
|
+
export const ADVISORS: string[];
|
|
5
|
+
export const MAX_RULES: number;
|
|
6
|
+
export const TOOLS: string[];
|
|
7
|
+
export function parseRule(rule: unknown, source?: string): { tool: string; specifier?: string };
|
|
8
|
+
export function validatePermissions(value: unknown, source: string): { profiles: Record<string, { base?: PermissionBase; beyondMode?: 'refuse' | 'ask'; workdir?: 'copy'; allow: string[]; ask: string[]; deny: string[] }>; agents: Record<string, string> };
|
|
9
|
+
export function addAllowRule(projectRoot: string, home: string | undefined, profileName: string, rule: string): Promise<string>;
|
|
10
|
+
export function profileFor(projectRoot: string, home: string | undefined, agent: string): Promise<PermissionProfile | null>;
|
|
11
|
+
export function capMode(mode: string, base: string): string;
|
|
12
|
+
export function unwrapShell(command: string): string;
|
|
13
|
+
export function simpleCommands(line: string): string[] | null;
|
|
14
|
+
export function commandDecision(profile: Pick<PermissionProfile, 'allow' | 'deny'> & { ask?: string[] }, command: string): 'allow' | 'ask' | 'deny' | undefined;
|