@anhlp/alp-runtime 0.0.0-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (94) hide show
  1. package/LICENSE +202 -0
  2. package/NOTICE +7 -0
  3. package/README.md +7 -0
  4. package/dist/acp-transport.d.ts +114 -0
  5. package/dist/acp-transport.js +478 -0
  6. package/dist/antigravity-transport.d.ts +80 -0
  7. package/dist/antigravity-transport.js +609 -0
  8. package/dist/board-domain.d.ts +56 -0
  9. package/dist/board-domain.js +157 -0
  10. package/dist/board.d.ts +21 -0
  11. package/dist/board.js +69 -0
  12. package/dist/catalog.d.ts +31 -0
  13. package/dist/catalog.js +38 -0
  14. package/dist/claude-transport.d.ts +157 -0
  15. package/dist/claude-transport.js +671 -0
  16. package/dist/contracts.d.ts +163 -0
  17. package/dist/contracts.js +5 -0
  18. package/dist/events.d.ts +1 -0
  19. package/dist/events.js +0 -0
  20. package/dist/github.d.ts +10 -0
  21. package/dist/github.js +39 -0
  22. package/dist/handoff.d.ts +36 -0
  23. package/dist/handoff.js +73 -0
  24. package/dist/index.d.ts +24 -0
  25. package/dist/index.js +64 -0
  26. package/dist/language.d.ts +41 -0
  27. package/dist/language.js +68 -0
  28. package/dist/live.d.ts +48 -0
  29. package/dist/live.js +45 -0
  30. package/dist/mailbox.d.ts +24 -0
  31. package/dist/mailbox.js +36 -0
  32. package/dist/mcp-tools.d.ts +115 -0
  33. package/dist/mcp-tools.js +71 -0
  34. package/dist/permission-path.d.ts +2 -0
  35. package/dist/permission-path.js +44 -0
  36. package/dist/process-info.d.ts +14 -0
  37. package/dist/process-info.js +32 -0
  38. package/dist/providers.d.ts +28 -0
  39. package/dist/providers.js +71 -0
  40. package/dist/recall.d.ts +42 -0
  41. package/dist/recall.js +60 -0
  42. package/dist/resolve.d.ts +121 -0
  43. package/dist/resolve.js +238 -0
  44. package/dist/runtime-context.d.ts +43 -0
  45. package/dist/runtime-context.js +61 -0
  46. package/dist/runtime.d.ts +4 -0
  47. package/dist/runtime.js +2979 -0
  48. package/dist/session-config.d.ts +1973 -0
  49. package/dist/session-config.js +205 -0
  50. package/dist/session-state.d.ts +140 -0
  51. package/dist/session-state.js +0 -0
  52. package/dist/tool-results.d.ts +12 -0
  53. package/dist/tool-results.js +25 -0
  54. package/dist/tools.d.ts +674 -0
  55. package/dist/tools.js +375 -0
  56. package/dist/transport.d.ts +47 -0
  57. package/dist/transport.js +169 -0
  58. package/dist/usage.d.ts +15 -0
  59. package/dist/usage.js +35 -0
  60. package/dist/workspace.d.ts +101 -0
  61. package/dist/workspace.js +259 -0
  62. package/dist/worktree-domain.d.ts +88 -0
  63. package/dist/worktree-domain.js +196 -0
  64. package/package.json +256 -0
  65. package/src/acp-transport.ts +473 -0
  66. package/src/antigravity-transport.ts +658 -0
  67. package/src/board-domain.ts +185 -0
  68. package/src/board.ts +80 -0
  69. package/src/catalog.ts +32 -0
  70. package/src/claude-transport.ts +771 -0
  71. package/src/contracts.ts +166 -0
  72. package/src/events.ts +1 -0
  73. package/src/github.ts +37 -0
  74. package/src/handoff.ts +86 -0
  75. package/src/index.ts +26 -0
  76. package/src/language.ts +102 -0
  77. package/src/live.ts +86 -0
  78. package/src/mailbox.ts +46 -0
  79. package/src/mcp-tools.ts +73 -0
  80. package/src/permission-path.ts +37 -0
  81. package/src/process-info.ts +40 -0
  82. package/src/providers.ts +84 -0
  83. package/src/recall.ts +85 -0
  84. package/src/resolve.ts +230 -0
  85. package/src/runtime-context.ts +49 -0
  86. package/src/runtime.ts +3579 -0
  87. package/src/session-config.ts +301 -0
  88. package/src/session-state.ts +141 -0
  89. package/src/tool-results.ts +26 -0
  90. package/src/tools.ts +379 -0
  91. package/src/transport.ts +116 -0
  92. package/src/usage.ts +42 -0
  93. package/src/workspace.ts +318 -0
  94. package/src/worktree-domain.ts +216 -0
@@ -0,0 +1,238 @@
1
+ // packages/runtime/src/resolve.ts
2
+ import path from "node:path";
3
+ import { access, readFile, stat } from "node:fs/promises";
4
+ import { resolveWorkflow } from "@anhlp/alp-core/workflow";
5
+ import { initProject } from "@anhlp/alp-core/init";
6
+ import { profileModel, agentProfiles, selectedProfile } from "@anhlp/alp-core/profiles";
7
+ import { jsonObject, resolveAgent } from "@anhlp/alp-core/resolver";
8
+ import { resolveTeam, teamOf } from "@anhlp/alp-core/teams";
9
+ import { loadHooks } from "@anhlp/alp-core/hooks";
10
+ import { acpModel, loadProvider } from "@anhlp/alp-core/providers";
11
+ import { ensureLibrary } from "@anhlp/alp-core/library";
12
+ import { compileAgent } from "@anhlp/alp-core/adapter";
13
+ import { capMode, profileFor } from "@anhlp/alp-core/permissions";
14
+ import { userLanguage } from "@anhlp/alp-core/user-settings";
15
+ import { claudeSandboxAvailable } from "./claude-transport.js";
16
+ import { DEFAULT_ANTIGRAVITY_MODEL, DEFAULT_CLAUDE_MODEL, DEFAULT_MODEL, ORACLE_MODELS, ORACLE_THINKING, models, modes, thinkingOptions, thinkingOptionsFor } from "./catalog.js";
17
+ async function exists(target) {
18
+ try {
19
+ await access(target);
20
+ return true;
21
+ } catch (error) {
22
+ if (error?.code === "ENOENT") return false;
23
+ throw error;
24
+ }
25
+ }
26
+ var SKILL_DESCRIPTION_CHARS = 400;
27
+ async function skillDescription(file) {
28
+ const text = await readFile(file, "utf8").catch(() => "");
29
+ const front = /^---\r?\n([\s\S]*?)\r?\n---/.exec(text)?.[1] ?? "";
30
+ const match = /^description:[ \t]*(.*(?:\r?\n[ \t]+.*)*)/m.exec(front)?.[1] ?? "";
31
+ const flat = match.replace(/^[>|][-+]?/, "").replace(/\s+/g, " ").trim().replace(/^(['"])(.*)\1$/, "$2");
32
+ return flat.length > SKILL_DESCRIPTION_CHARS ? `${flat.slice(0, SKILL_DESCRIPTION_CHARS - 1)}\u2026` : flat;
33
+ }
34
+ var InstructionsAdapter = class {
35
+ id = "native";
36
+ // ALP runs an agent's hooks itself, at its own events (ALPD §45).
37
+ capabilities() {
38
+ return { instructions: "emulated", skills: "emulated", hooks: "emulated", mcp: "native" };
39
+ }
40
+ async compile(agent) {
41
+ const listed = await Promise.all(agent.skills.map(async (skill) => {
42
+ const description = await skillDescription(skill.path);
43
+ return `- ${skill.name} (${skill.path})${description ? `: ${description}` : ""}`;
44
+ }));
45
+ const skills = listed.length ? "Your skills: methods written for kinds of work. Before you start work that a skill's description matches, read its SKILL.md and follow it; say which skill you use.\n" + listed.join("\n") : "";
46
+ return { adapterId: this.id, agentName: agent.name, projectRoot: agent.projectRoot, material: {
47
+ instructions: [agent.instructions.project, agent.instructions.agent, skills].filter(Boolean).join("\n\n"),
48
+ mcp: agent.mcp,
49
+ runtime: agent.runtime
50
+ } };
51
+ }
52
+ };
53
+ var LESSONS_FILE = "lessons.md";
54
+ var LESSON_CHARS = 6e3;
55
+ async function lessons(file) {
56
+ const text = (await readFile(file, "utf8").catch(() => "")).trim();
57
+ return text.length > LESSON_CHARS ? `\u2026
58
+ ${text.slice(-LESSON_CHARS)}` : text;
59
+ }
60
+ var READ_ONLY_AGENTS = ["oracle", "reviewer", "supervisor"];
61
+ async function resolveSession(spec, options = {}) {
62
+ if (!path.isAbsolute(spec.cwd) || !(await stat(spec.cwd)).isDirectory()) throw new Error("Session cwd must be an existing absolute directory");
63
+ if (spec.workdir !== void 0 && (!path.isAbsolute(spec.workdir) || !(await stat(spec.workdir)).isDirectory())) throw new Error("Session workdir must be an existing absolute directory");
64
+ const starter = options.templates ? { templates: options.templates } : {};
65
+ if (options.initialize !== false) {
66
+ const settingsFile = path.join(spec.cwd, ".alp", "settings.json");
67
+ const scaffoldMissing = !await exists(path.join(spec.cwd, "ALP.md")) || !await exists(settingsFile);
68
+ if (scaffoldMissing) await initProject(spec.cwd, { ...starter, library: options.library });
69
+ else if (!spec.restore) {
70
+ const settings = await jsonObject(settingsFile, "INVALID_SETTINGS");
71
+ await initProject(spec.cwd, { ...starter, library: options.library, onlyManagedProfiles: true, teamIds: [teamOf(spec.workflow ?? settings.workflow?.mode ?? "custom")] });
72
+ }
73
+ }
74
+ if (options.library) await ensureLibrary(options.library, options.templates ? { templates: options.templates } : {});
75
+ const restored = spec.restore;
76
+ if (restored && spec.agent !== void 0 && spec.agent !== restored.agent) throw new Error("Cannot resume a thread as a different ALP agent");
77
+ const { team, ...workflow } = await resolveWorkflow(spec.cwd, spec.workflow, restored?.workflow, { library: options.library, templates: options.templates });
78
+ const agent = await resolveAgent(spec.cwd, { agent: spec.agent ?? restored?.agent, library: options.library, templates: options.templates });
79
+ const oracleModels = team?.oracleModels ?? ORACLE_MODELS;
80
+ if (agent.name === "oracle" && !restored && !oracleModels.includes(spec.model ?? "")) throw new Error(`Oracle runs on ${oracleModels.join(" or ")}; choose one`);
81
+ const projectSettings = await jsonObject(path.join(spec.cwd, ".alp/settings.json"), "INVALID_SETTINGS");
82
+ const profile = (projectSettings.team ?? projectSettings.profile)?.[workflow.mode]?.[agent.name];
83
+ const modelProfiles = Object.fromEntries(Object.entries((projectSettings.team ?? projectSettings.profile)?.[workflow.mode] ?? {}).map(([name, config]) => [name, agentProfiles(config, name)]));
84
+ const requestedModel = restored?.model ? `${restored.runtime ?? agent.runtime.provider ?? "codex"}:${restored.model}` : spec.model;
85
+ const profileChoice = selectedProfile(modelProfiles[agent.name] ?? [], requestedModel);
86
+ const projectModel = profileModel(profileChoice);
87
+ const compiled = await compileAgent(new InstructionsAdapter(), agent);
88
+ const base = team ?? await resolveTeam(void 0, "pho", { templates: options.templates });
89
+ const main = base.main;
90
+ const member = Object.hasOwn(base.members, agent.name) && (team || agent.name === main) ? base.members[agent.name] : void 0;
91
+ const sameModel = (id) => id === void 0 || restored?.model === void 0 || `${restored.runtime}:${restored.model}` === id || restored.model === id;
92
+ const teamChoice = member && !agent.projectRuntime?.model && !agent.projectRuntime?.provider && spec.model === void 0 && sameModel(member.model) ? member : void 0;
93
+ let runtimeKind = restored?.runtime ?? profileChoice?.provider ?? (teamChoice?.model ? void 0 : agent.runtime.provider) ?? "codex";
94
+ let model = restored?.model ?? spec.model ?? projectModel ?? teamChoice?.model ?? agent.runtime.model ?? (runtimeKind === "claude" ? DEFAULT_CLAUDE_MODEL : DEFAULT_MODEL);
95
+ if (model.startsWith("codex:")) {
96
+ runtimeKind = "codex";
97
+ model = model.slice("codex:".length);
98
+ }
99
+ if (model.startsWith("claude:")) {
100
+ runtimeKind = "claude";
101
+ model = model.slice("claude:".length);
102
+ }
103
+ if (model.startsWith("codex/")) {
104
+ runtimeKind = "codex";
105
+ model = model.slice("codex/".length);
106
+ }
107
+ if (model.startsWith("claude/")) {
108
+ runtimeKind = "claude";
109
+ model = model.slice("claude/".length);
110
+ }
111
+ if (model.startsWith("antigravity:")) {
112
+ runtimeKind = "antigravity";
113
+ model = model.slice("antigravity:".length);
114
+ }
115
+ if (model.startsWith("antigravity/")) {
116
+ runtimeKind = "antigravity";
117
+ model = model.slice("antigravity/".length);
118
+ }
119
+ if (model.startsWith("acp:")) {
120
+ const raw = model.slice("acp:".length);
121
+ if (raw.startsWith("antigravity/") || raw === "antigravity") {
122
+ runtimeKind = "antigravity";
123
+ model = raw.startsWith("antigravity/") ? raw.slice("antigravity/".length) : "";
124
+ } else {
125
+ runtimeKind = "acp";
126
+ model = raw;
127
+ }
128
+ }
129
+ if (runtimeKind === "antigravity") {
130
+ if (!model.trim() || model === DEFAULT_MODEL) model = DEFAULT_ANTIGRAVITY_MODEL;
131
+ }
132
+ if (!["codex", "claude", "acp", "antigravity"].includes(runtimeKind)) {
133
+ model = model === DEFAULT_MODEL && agent.runtime.model !== DEFAULT_MODEL ? runtimeKind : `${runtimeKind}/${model}`;
134
+ runtimeKind = "acp";
135
+ }
136
+ if (restored?.runtime && runtimeKind !== restored.runtime) throw new Error("Cannot resume a thread with a different runtime provider");
137
+ let acp;
138
+ if (runtimeKind === "acp") {
139
+ const { provider: id, model: native } = acpModel(model);
140
+ acp = await loadProvider(options.library, id);
141
+ if (!acp) throw new Error(`No ACP provider '${id}': add one to your library with alp provider add ${id}, or name codex or claude`);
142
+ if (acp.enabled === false) throw new Error(`ACP provider '${id}' is turned off; turn it on in the web app's Providers settings or in providers/${id}.json`);
143
+ const listed = [...acp.models ?? [], ...acp.additionalModels ?? []];
144
+ if (native && acp.models && !listed.some((candidate) => candidate.id === native)) throw new Error(`ACP provider '${id}' lists no model '${native}'`);
145
+ if (spec.copy) throw new Error("A review copy cannot run on an ACP agent: ALP cannot keep its commands inside the copy");
146
+ }
147
+ let antigravity;
148
+ if (runtimeKind === "antigravity") {
149
+ const provider = await loadProvider(options.library, "antigravity");
150
+ if (provider && provider.kind === "antigravity") {
151
+ antigravity = provider;
152
+ if (antigravity.enabled === false) throw new Error("Antigravity provider 'antigravity' is turned off; turn it on in the web app's Providers settings or in providers/antigravity.json");
153
+ }
154
+ }
155
+ const permissions = await profileFor(agent.projectRoot, options.library, agent.name);
156
+ const requested = spec.mode ?? agent.mode ?? (agent.name === main ? "full-access" : "read-only");
157
+ const mode = permissions ? capMode(requested, permissions.base) : requested;
158
+ if (runtimeKind === "antigravity") {
159
+ if (mode !== "full-access" && !(mode === "read-only" && antigravity?.readOnlyMode === "instructions")) throw new Error('Antigravity currently supports only full-access; read-only requires explicit readOnlyMode: "instructions" and is not sandboxed');
160
+ if (spec.copy || permissions?.workdir === "copy") throw new Error("Antigravity cannot enforce isolation for a review copy");
161
+ if (permissions && (permissions.deny.length || permissions.ask.length || permissions.allow.length || permissions.beyondMode === "ask")) {
162
+ throw new Error("Antigravity cannot enforce custom permission rules");
163
+ }
164
+ }
165
+ const availableThinking = ["acp", "antigravity"].includes(runtimeKind) ? [] : thinkingOptionsFor(runtimeKind, model);
166
+ const preferred = ["acp", "antigravity"].includes(runtimeKind) ? "none" : spec.thinking ?? profileChoice?.effort ?? agent.projectRuntime?.reasoning ?? (agent.name === "oracle" ? team?.oracleThinking?.[`${runtimeKind}:${model}`] ?? team?.oracleThinking?.[runtimeKind] : void 0) ?? teamChoice?.thinking ?? agent.runtime.reasoning ?? (agent.name === "oracle" ? ORACLE_THINKING : availableThinking.length ? "medium" : "none");
167
+ const thinking = spec.thinking === void 0 && availableThinking.length && !availableThinking.some((candidate) => candidate.id === preferred) ? models.find((candidate) => candidate.id === `${runtimeKind}:${model}`)?.defaultThinkingOptionId ?? availableThinking[0].id : preferred;
168
+ if (!model.trim()) throw new Error("Model must be nonempty");
169
+ if (!modes.some((m) => m.id === mode)) throw new Error(`Unsupported mode '${mode}'`);
170
+ if (spec.copy && runtimeKind === "claude" && !claudeSandboxAvailable()) throw new Error("A review copy on Claude needs its Bash sandbox (Seatbelt on macOS; bubblewrap and socat on Linux)");
171
+ if (!thinkingOptions.some((m) => m.id === thinking)) throw new Error(`Unsupported thinking option '${thinking}'`);
172
+ if (availableThinking.length && !availableThinking.some((candidate) => candidate.id === thinking)) throw new Error(`Thinking option '${thinking}' is unsupported by ${runtimeKind}:${model}`);
173
+ const mcp = {};
174
+ for (const [name, value] of Object.entries(compiled.material.mcp.mcpServers)) {
175
+ const { headers, ...rest } = value;
176
+ Object.defineProperty(mcp, name, { value: { ...rest, ...headers ? { http_headers: headers } : {} }, enumerable: true, writable: true });
177
+ }
178
+ for (const [name, value] of Object.entries(spec.mcpServers ?? {})) {
179
+ if (Object.hasOwn(mcp, name)) throw new Error(`MCP server name collision: '${name}'`);
180
+ if (value.type === "sse") throw new Error(`SSE MCP server '${name}' is unsupported by this runtime`);
181
+ const server = value.type === "stdio" ? { command: value.command, args: value.args ?? [], env: value.env ?? {} } : { url: value.url, http_headers: value.headers ?? {} };
182
+ Object.defineProperty(mcp, name, { value: server, enumerable: true, writable: true });
183
+ }
184
+ let learned = "";
185
+ const supervisorAgent = team?.supervisor ? team.supervisor.agent : void 0;
186
+ if ([main, supervisorAgent].includes(agent.name) && workflow.supervisor) {
187
+ const user = options.library ? await lessons(path.join(options.library, LESSONS_FILE)) : "";
188
+ const project = await lessons(path.join(agent.projectRoot, ".alp", LESSONS_FILE));
189
+ if (user || project) {
190
+ learned = "Lessons main recorded after earlier supervisor reviews. Follow them; they are the user's process, not a task." + (user ? `
191
+
192
+ For every project:
193
+ ${user}` : "") + (project ? `
194
+
195
+ For this project:
196
+ ${project}` : "");
197
+ }
198
+ }
199
+ const houseRules = base.houseRules;
200
+ const hooks = await loadHooks(agent);
201
+ return {
202
+ agent,
203
+ workflow,
204
+ team,
205
+ oracleModels,
206
+ modelProfiles,
207
+ houseRules,
208
+ hooks,
209
+ runtimeKind,
210
+ model,
211
+ ...acp ? { acp } : {},
212
+ ...antigravity ? { antigravity } : {},
213
+ mode,
214
+ permissions,
215
+ thinking,
216
+ threadId: restored?.threadId,
217
+ workdir: spec.workdir ?? agent.projectRoot,
218
+ copy: Boolean(spec.copy && spec.workdir),
219
+ ...spec.copy && spec.copyOf ? { copyOf: spec.copyOf } : {},
220
+ instructions: [compiled.material.instructions, learned, spec.systemPrompt].filter(Boolean).join("\n\n"),
221
+ mcp,
222
+ env: { ...spec.env },
223
+ persist: spec.persist ?? false,
224
+ keepThread: spec.keepThread ?? false,
225
+ // ALPD §57: the window the agent works in before its runtime compacts; absent, the model's.
226
+ ...agent.context ? { context: agent.context } : {},
227
+ autocompact: typeof profile?.autocompact === "number" ? profile.autocompact : typeof agent.autocompact === "number" ? agent.autocompact : void 0,
228
+ // ALPD §54: the language everything the user reads is written in.
229
+ language: options.language ?? await userLanguage(options.library)
230
+ };
231
+ }
232
+ export {
233
+ InstructionsAdapter,
234
+ LESSONS_FILE,
235
+ READ_ONLY_AGENTS,
236
+ resolveSession,
237
+ skillDescription
238
+ };
@@ -0,0 +1,43 @@
1
+ export declare const COMPACT_SHARE = 0.9;
2
+ /** Bounded optional reads. Never expose account identifiers or credentials. */
3
+ export declare function optionalRead<T>(read: () => Promise<T>, timeout?: number): Promise<T | undefined>;
4
+ export declare function codexUsage(result: any): {
5
+ available: boolean;
6
+ ordinaryUsageAllowed?: undefined;
7
+ limits?: undefined;
8
+ } | {
9
+ available: boolean;
10
+ ordinaryUsageAllowed: any;
11
+ limits: {
12
+ name: any;
13
+ model: any;
14
+ plan: any;
15
+ windows: {
16
+ usedPercent: any;
17
+ remainingPercent: number | null;
18
+ windowMinutes: any;
19
+ resetsAt: any;
20
+ }[];
21
+ spendControlReached: any;
22
+ }[];
23
+ };
24
+ export declare function claudeUsage(result: any): {
25
+ available: boolean;
26
+ plan?: undefined;
27
+ windows?: undefined;
28
+ } | {
29
+ available: boolean;
30
+ plan: any;
31
+ windows: {
32
+ name: string;
33
+ usedPercent: any;
34
+ remainingPercent: number | null;
35
+ resetsAt: any;
36
+ }[];
37
+ };
38
+ /**
39
+ * Whether a runtime's usage report (codexUsage or claudeUsage) lets it work again (ALPD §58):
40
+ * true when every window has room, false when one is used up or spending is capped, and
41
+ * undefined when the report says nothing either way.
42
+ */
43
+ export declare function usageAllows(usage: any): boolean | undefined;
@@ -0,0 +1,61 @@
1
+ // packages/runtime/src/runtime-context.ts
2
+ var COMPACT_SHARE = 0.9;
3
+ async function optionalRead(read, timeout = 1500) {
4
+ let timer;
5
+ try {
6
+ return await Promise.race([Promise.resolve().then(read).catch(() => void 0), new Promise((resolve) => {
7
+ timer = setTimeout(() => resolve(void 0), timeout);
8
+ })]);
9
+ } finally {
10
+ clearTimeout(timer);
11
+ }
12
+ }
13
+ function codexUsage(result) {
14
+ if (!result?.rateLimits) return { available: false };
15
+ const clean = (limit) => ({
16
+ name: limit?.limitName ?? null,
17
+ model: limit?.normalModelSlug ?? null,
18
+ plan: limit?.planType ?? null,
19
+ windows: [limit?.primary, limit?.secondary].filter(Boolean).map((window) => ({
20
+ usedPercent: window.usedPercent,
21
+ remainingPercent: typeof window.usedPercent === "number" ? Math.max(0, 100 - window.usedPercent) : null,
22
+ windowMinutes: window.windowDurationMins,
23
+ resetsAt: window.resetsAt
24
+ })),
25
+ spendControlReached: limit?.spendControlReached ?? null
26
+ });
27
+ return {
28
+ available: true,
29
+ ordinaryUsageAllowed: result.ordinaryUsageAllowed ?? null,
30
+ limits: Object.values(result.rateLimitsByLimitId ?? { default: result.rateLimits }).map(clean)
31
+ };
32
+ }
33
+ function claudeUsage(result) {
34
+ if (!result) return { available: false };
35
+ return {
36
+ available: result.rate_limits_available === true,
37
+ plan: result.subscription_type ?? null,
38
+ windows: Object.entries(result.rate_limits ?? {}).flatMap(([name, window]) => window && "utilization" in window ? [{
39
+ name,
40
+ usedPercent: window.utilization,
41
+ remainingPercent: typeof window.utilization === "number" ? Math.max(0, 100 - window.utilization) : null,
42
+ resetsAt: window.resets_at
43
+ }] : [])
44
+ };
45
+ }
46
+ function usageAllows(usage) {
47
+ if (!usage?.available) return void 0;
48
+ const limits = Array.isArray(usage.limits) ? usage.limits : [];
49
+ if (limits.some((limit) => limit?.spendControlReached === true)) return false;
50
+ const windows = limits.length ? limits.flatMap((limit) => limit?.windows ?? []) : usage.windows ?? [];
51
+ const known = windows.filter((window) => typeof window?.usedPercent === "number");
52
+ if (!known.length) return void 0;
53
+ return known.every((window) => window.usedPercent < 100);
54
+ }
55
+ export {
56
+ COMPACT_SHARE,
57
+ claudeUsage,
58
+ codexUsage,
59
+ optionalRead,
60
+ usageAllows
61
+ };
@@ -0,0 +1,4 @@
1
+ import { type RuntimeOptions, type AlpRuntime } from './contracts.js';
2
+ export type { RuntimeTransport, RuntimeOptions, OpenOptions, PromptContent, PromptInput, AlpRuntime, SessionPreview, RecoveryOutcome, Pause, PauseState, RecallAnswer } from './contracts.js';
3
+ export { DEFAULT_TEAM } from './contracts.js';
4
+ export declare function createAlpRuntime(options?: RuntimeOptions): AlpRuntime;