@anhlp/alp 0.0.0-beta.2 → 0.0.1-beta.3

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.
@@ -138,7 +138,7 @@ Restarting is non-destructive. `alp daemon restart` (or the plugin's supervision
138
138
 
139
139
  ## Independent versions and publishing
140
140
 
141
- > **For the beta `0.0.0-beta.2`, do not use the commands in this section.** The beta gives all
141
+ > **For the beta `0.0.1-beta.3`, do not use the commands in this section.** The beta gives all
142
142
  > eight published packages one version, packs them once and publishes those tarballs with
143
143
  > `--tag beta`, following the manual checklist in [beta-scope.md](alp/beta-scope.md) §7. The
144
144
  > `npm publish` below has no `--tag`, so it would set `latest`.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@anhlp/alp",
3
- "version": "0.0.0-beta.2",
3
+ "version": "0.0.1-beta.3",
4
4
  "description": "ALP command line and alpd, the per-user daemon that runs filesystem-defined Codex and Claude Code agent teams",
5
5
  "type": "module",
6
6
  "files": [
@@ -91,9 +91,9 @@
91
91
  "zod": "4.4.3"
92
92
  },
93
93
  "dependencies": {
94
- "@anhlp/alp-client": "0.0.0-beta.2",
95
- "@anhlp/alp-core": "0.0.0-beta.2",
96
- "@anhlp/alp-protocol": "0.0.0-beta.2",
94
+ "@anhlp/alp-client": "0.0.1-beta.3",
95
+ "@anhlp/alp-core": "0.0.1-beta.3",
96
+ "@anhlp/alp-protocol": "0.0.1-beta.3",
97
97
  "@anthropic-ai/claude-agent-sdk": "^0.3.292",
98
98
  "smol-toml": "1.9.0"
99
99
  },
@@ -6,7 +6,7 @@ import { startDaemon } from "@anhlp/alp-client/supervise";
6
6
  import { createLogger } from "@anhlp/alp-core/log";
7
7
  import { createAcpAgent } from "./agent.js";
8
8
  async function runAcp({ daemonEntry }) {
9
- const version = true ? "0.0.0-beta.2" : "0.0.0";
9
+ const version = true ? "0.0.1-beta.3" : "0.0.0";
10
10
  const logger = createLogger({ component: "acp" });
11
11
  const log = (message, fields) => logger.warn(message, fields);
12
12
  const app = createAcpAgent({
@@ -18419,8 +18419,8 @@ import { Readable, Writable } from "node:stream";
18419
18419
 
18420
18420
  // packages/client/src/index.js
18421
18421
  import { spawn } from "node:child_process";
18422
- import { createHash } from "node:crypto";
18423
- import { open, readFile, realpath, stat, lstat, mkdir, readlink, unlink } from "node:fs/promises";
18422
+ import { createHash, randomUUID } from "node:crypto";
18423
+ import { open, readFile, writeFile, realpath, stat, lstat, mkdir, readlink, unlink, readdir, rename, rmdir, link } from "node:fs/promises";
18424
18424
  import net from "node:net";
18425
18425
  import os from "node:os";
18426
18426
  import path from "node:path";
@@ -18552,7 +18552,7 @@ var AlpRpcError = class extends Error {
18552
18552
  };
18553
18553
  var ERROR = Object.freeze({ failed: 1e3, notFound: 1001, forbidden: 1003, conflict: 1004, unavailable: 1005, protocol: 1006 });
18554
18554
  var RPC_ERROR = Object.freeze({ parseError: -32700, invalidRequest: -32600, methodNotFound: -32601, invalidParams: -32602 });
18555
- var CAPABILITY = Object.freeze({ attachAfter: "attach.after", promptReceipts: "prompt.receipts", extensionMethods: "extension.methods", team: "team", task: "task", library: "library", settingsLanguage: "settings.language", sessionListPage: "session.list.page", questionWatch: "question.watch", questionWatchTurns: "question.watch.turns", workspaceFiles: "workspace.files", workspaceFilesRead: "workspace.files.read", promptImages: "prompt.images" });
18555
+ var CAPABILITY = Object.freeze({ attachAfter: "attach.after", promptReceipts: "prompt.receipts", extensionMethods: "extension.methods", team: "team", task: "task", library: "library", libraryProjectWrite: "library.project.write", settingsLanguage: "settings.language", sessionListPage: "session.list.page", questionWatch: "question.watch", questionWatchTurns: "question.watch.turns", workspaceFiles: "workspace.files", workspaceFilesRead: "workspace.files.read", promptImages: "prompt.images" });
18556
18556
  var PROMPT_IMAGES = Object.freeze({ mimeTypes: Object.freeze(["image/png", "image/jpeg", "image/gif", "image/webp"]), maxCount: 8, maxBytes: 5 * 1024 * 1024, maxTotalBytes: 11 * 1024 * 1024 });
18557
18557
  var RPC_METHODS = Object.freeze(["assignment.recall", "board.list", "catalog.get", "daemon.hello", "daemon.pause", "daemon.pauses", "daemon.resume", "daemon.shutdown", "daemon.status", "daemon.usage", "extension.register", "extension.unregister", "library.delete", "library.duplicate", "library.get", "library.list", "library.models", "library.rename", "library.save", "library.skills.set", "question.answer", "question.list", "question.watch", "session.archive", "session.attach", "session.close", "session.configure", "session.create", "session.get", "session.interrupt", "session.list", "session.log", "session.message", "session.preview", "session.prompt", "session.release", "session.rename", "session.status", "settings.language.get", "settings.language.set", "task.create", "task.gate.resolve", "task.list", "task.update", "team.list", "workspace.files.list", "workspace.files.read", "workspace.files.search"]);
18558
18558
  var WORKSPACE_FILES = Object.freeze({ maxReadBytes: 1024 * 1024, maxListEntries: 1e3, maxPathLength: 4096 });
@@ -19158,10 +19158,86 @@ async function tail(file2, lines = 30) {
19158
19158
  return "";
19159
19159
  }
19160
19160
  }
19161
+ async function lifecycleOwners(home) {
19162
+ const directory = path.join(home, "state", "alpd.starting");
19163
+ const names = await readdir(directory).catch((error51) => {
19164
+ if (error51.code === "ENOENT") return [];
19165
+ throw error51;
19166
+ });
19167
+ return names.map((token) => {
19168
+ const match = /^owner-(\d+)-(\d+)-(start|stop)-[a-f0-9-]+$/.exec(token);
19169
+ if (!match) throw new Error("Invalid alpd startup admission owner");
19170
+ return { token, pid: Number(match[1]), bootTime: Number(match[2]), action: match[3] };
19171
+ });
19172
+ }
19173
+ async function readDaemonLifecycle(home) {
19174
+ return (await lifecycleOwners(home)).find((owner) => lockAlive(owner));
19175
+ }
19176
+ async function withDaemonLifecycle(home, action, run, { joinStart = false } = {}) {
19177
+ try {
19178
+ await assertPrivateDirectory(home, { create: true });
19179
+ } catch (cause) {
19180
+ throw Object.assign(new Error(`ALP_HOME_UNUSABLE, alpd failed to start while preparing its home: ${cause.message}`, { cause }), { code: "ALP_HOME_UNUSABLE" });
19181
+ }
19182
+ const state = path.join(home, "state");
19183
+ await assertPrivateDirectory(state, { create: true });
19184
+ const directory = path.join(state, "alpd.starting");
19185
+ const token = `owner-${process.pid}-${bootTime()}-${action}-${randomUUID()}`;
19186
+ const candidate = path.join(state, `alpd.starting-${token}`);
19187
+ await mkdir(candidate, { mode: 448 });
19188
+ await writeFile(path.join(candidate, token), JSON.stringify({ pid: process.pid, bootTime: bootTime(), action }), { mode: 384 });
19189
+ try {
19190
+ let joined = false;
19191
+ if (joinStart) {
19192
+ const owner = await readDaemonLifecycle(home);
19193
+ if (owner?.action === "start") {
19194
+ try {
19195
+ await link(path.join(directory, owner.token), path.join(directory, token));
19196
+ joined = true;
19197
+ } catch (error51) {
19198
+ if (error51.code !== "ENOENT") throw error51;
19199
+ }
19200
+ }
19201
+ }
19202
+ while (!joined) {
19203
+ try {
19204
+ await rename(candidate, directory);
19205
+ break;
19206
+ } catch (error51) {
19207
+ if (!["EEXIST", "ENOTEMPTY", "EPERM", "EACCES"].includes(error51.code)) throw error51;
19208
+ for (const owner of await lifecycleOwners(home)) {
19209
+ if (!lockAlive(owner)) await unlink(path.join(directory, owner.token)).catch((error52) => {
19210
+ if (error52.code !== "ENOENT") throw error52;
19211
+ });
19212
+ }
19213
+ await rmdir(directory).catch((error52) => {
19214
+ if (!["ENOENT", "ENOTEMPTY", "EEXIST"].includes(error52.code)) throw error52;
19215
+ });
19216
+ await new Promise((resolve) => setTimeout(resolve, 25));
19217
+ }
19218
+ }
19219
+ try {
19220
+ return await run();
19221
+ } finally {
19222
+ await unlink(path.join(directory, token));
19223
+ await rmdir(directory).catch((error51) => {
19224
+ if (!["ENOENT", "ENOTEMPTY", "EEXIST"].includes(error51.code)) throw error51;
19225
+ });
19226
+ }
19227
+ } finally {
19228
+ await unlink(path.join(candidate, token)).catch((error51) => {
19229
+ if (error51.code !== "ENOENT") throw error51;
19230
+ });
19231
+ await rmdir(candidate).catch((error51) => {
19232
+ if (error51.code !== "ENOENT") throw error51;
19233
+ });
19234
+ }
19235
+ }
19161
19236
  async function ensureDaemon({ home = alpHome(), entry = (
19162
19237
  /** @type {string | undefined} */
19163
19238
  void 0
19164
- ), env = process.env, execPath = process.execPath, timeoutMs = 15e3 } = {}) {
19239
+ ), env = process.env, execPath = process.execPath, timeoutMs = 15e3, lifecycleLocked = false } = {}) {
19240
+ if (!lifecycleLocked) return withDaemonLifecycle(home, "start", () => ensureDaemon({ home, entry, env, execPath, timeoutMs, lifecycleLocked: true }));
19165
19241
  const ready = async () => {
19166
19242
  const lock = await readLock(home);
19167
19243
  return lockAlive(lock) && lock.ready ? lock.socket : void 0;
@@ -19174,7 +19250,7 @@ async function ensureDaemon({ home = alpHome(), entry = (
19174
19250
  entry ??= await findDaemonEntry({ home, env });
19175
19251
  if (!entry) throw new Error("ALP daemon is not running and alpd.js was not found. Install the ALP CLI and run `alp daemon start`, or set ALP_DAEMON_ENTRY to alpd.js");
19176
19252
  await new Promise((resolve, reject) => {
19177
- const launcher = spawn(execPath, [entry, "--detach"], {
19253
+ const launcher = spawn(execPath, [entry, "--detach", `--startup-timeout=${timeoutMs}`], {
19178
19254
  env: { ...env, ALP_HOME: home, ELECTRON_RUN_AS_NODE: "1" },
19179
19255
  stdio: ["ignore", "ignore", "pipe"],
19180
19256
  windowsHide: true
@@ -19199,12 +19275,12 @@ ${log}` : ""}`);
19199
19275
  }
19200
19276
 
19201
19277
  // packages/client/src/supervise.js
19202
- import { mkdir as mkdir3, unlink as unlink3, writeFile as writeFile2, stat as stat2 } from "node:fs/promises";
19278
+ import { mkdir as mkdir3, unlink as unlink3, writeFile as writeFile3, stat as stat2 } from "node:fs/promises";
19203
19279
  import path3 from "node:path";
19204
19280
 
19205
19281
  // packages/client/src/service.js
19206
19282
  import { createHash as createHash2 } from "node:crypto";
19207
- import { mkdir as mkdir2, readFile as readFile2, unlink as unlink2, writeFile } from "node:fs/promises";
19283
+ import { mkdir as mkdir2, readFile as readFile2, unlink as unlink2, writeFile as writeFile2 } from "node:fs/promises";
19208
19284
  import os2 from "node:os";
19209
19285
  import path2 from "node:path";
19210
19286
 
@@ -19301,6 +19377,9 @@ async function releaseHold(home) {
19301
19377
  await unlink3(holdFile(home)).catch(() => {
19302
19378
  });
19303
19379
  }
19380
+ async function daemonHeld(home) {
19381
+ return stat2(holdFile(home)).then(() => true, () => false);
19382
+ }
19304
19383
  async function whenReady(home, timeoutMs) {
19305
19384
  const deadline = Date.now() + timeoutMs;
19306
19385
  while (Date.now() < deadline) {
@@ -19310,20 +19389,24 @@ async function whenReady(home, timeoutMs) {
19310
19389
  }
19311
19390
  throw new Error(`alpd did not become ready within ${timeoutMs / 1e3} s; see ${path3.join(home, "logs")}`);
19312
19391
  }
19313
- async function startDaemon({ home, entry, env = process.env, timeoutMs = 15e3 }) {
19314
- await releaseHold(home);
19315
- const lock = await readLock(home);
19316
- if (lockAlive(lock)) return lock.ready ? lock.socket : whenReady(home, timeoutMs);
19317
- const service = serviceFor({ home, env });
19318
- if (service && await installedProgram(service)) {
19319
- await startService(service);
19320
- return whenReady(home, timeoutMs);
19321
- }
19322
- return ensureDaemon({ home, entry, env, timeoutMs });
19392
+ async function startDaemon({ home, entry, env = process.env, timeoutMs = 15e3, automatic = false }) {
19393
+ return withDaemonLifecycle(home, "start", async () => {
19394
+ if (automatic) {
19395
+ if (await daemonHeld(home)) return;
19396
+ } else await releaseHold(home);
19397
+ const lock = await readLock(home);
19398
+ if (lockAlive(lock)) return lock.ready ? lock.socket : whenReady(home, timeoutMs);
19399
+ const service = serviceFor({ home, env });
19400
+ if (service && await installedProgram(service)) {
19401
+ await startService(service);
19402
+ return whenReady(home, timeoutMs);
19403
+ }
19404
+ return ensureDaemon({ home, entry, env, timeoutMs, lifecycleLocked: true });
19405
+ });
19323
19406
  }
19324
19407
 
19325
19408
  // packages/cli/src/acp/agent.ts
19326
- import { randomUUID } from "node:crypto";
19409
+ import { randomUUID as randomUUID2 } from "node:crypto";
19327
19410
  import path4 from "node:path";
19328
19411
  var unlistedOf = ({ teams, team, teamLabel }) => teams.some((entry) => entry.id === team) ? void 0 : { id: team, label: teamLabel ?? (team === "custom" ? "Custom" : team) };
19329
19412
  var MODES = [
@@ -19681,7 +19764,7 @@ ${item2.text}`;
19681
19764
  send(session, { sessionUpdate: "config_option_update", configOptions: configOptions(session) });
19682
19765
  }
19683
19766
  if (session.waiting) throw RequestError.invalidRequest(void 0, "A prompt is already running in this session");
19684
- const clientMessageId = randomUUID();
19767
+ const clientMessageId = randomUUID2();
19685
19768
  session.ours.add(clientMessageId);
19686
19769
  let done;
19687
19770
  const ended = new Promise((resolve) => {
@@ -19726,7 +19809,7 @@ ${item2.text}`;
19726
19809
  })).onRequest("authenticate", () => ({})).onRequest("session/new", async ({ params }) => {
19727
19810
  const cwd = absolute(params.cwd);
19728
19811
  const mcpServers = hostServers(params.mcpServers);
19729
- const session = track(`acp-${randomUUID()}`, cwd, mcpServers, await preview(cwd, mcpServers), false);
19812
+ const session = track(`acp-${randomUUID2()}`, cwd, mcpServers, await preview(cwd, mcpServers), false);
19730
19813
  queueMicrotask(() => send(session, { sessionUpdate: "available_commands_update", availableCommands: COMMANDS }));
19731
19814
  return { sessionId: session.id, configOptions: configOptions(session), modes: modeState(session) };
19732
19815
  }).onRequest("session/load", async ({ params }) => {
@@ -19803,7 +19886,7 @@ ${item2.text}`;
19803
19886
 
19804
19887
  // packages/cli/src/acp/main.ts
19805
19888
  async function runAcp({ daemonEntry }) {
19806
- const version2 = true ? "0.0.0-beta.2" : "0.0.0";
19889
+ const version2 = true ? "0.0.1-beta.3" : "0.0.0";
19807
19890
  const logger = createLogger({ component: "acp" });
19808
19891
  const log = (message, fields) => logger.warn(message, fields);
19809
19892
  const app = createAcpAgent({