@anhlp/alp-protocol 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/src/wire.js CHANGED
@@ -17,8 +17,69 @@ export const RPC_ERROR = Object.freeze({ parseError: -32700, invalidRequest: -32
17
17
  * Features a daemon lists in its daemon.hello result. A client uses a feature only when listed;
18
18
  * a daemon that lists none (one older than this list) has none of them. Unknown names are ignored.
19
19
  */
20
- export const CAPABILITY = Object.freeze({ attachAfter: 'attach.after', promptReceipts: 'prompt.receipts', extensionMethods: 'extension.methods', team: 'team', task: 'task', library: 'library', settingsLanguage: 'settings.language' });
21
- export const 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", "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"]);
20
+ export const 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' });
21
+ /**
22
+ * What alpd takes as images in one session.prompt (capability `prompt.images`): inline base64 of
23
+ * these types only, never a path or URL; at most `maxCount` of them, each at most `maxBytes`
24
+ * decoded, and `maxTotalBytes` together, so their base64 fits one MAX_FRAME. A gateway may allow
25
+ * less (docs/alp/remote-gateway.md).
26
+ */
27
+ export const 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 });
28
+ /** Decoded bytes of base64 `data` (padding aware), without decoding it. @param {string} data */
29
+ export const base64Bytes = data => Math.floor(data.length * 3 / 4) - (data.endsWith('==') ? 2 : data.endsWith('=') ? 1 : 0);
30
+ export const 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"]);
31
+
32
+ /**
33
+ * Limits of workspace.files.list and workspace.files.read (capability `workspace.files.read`,
34
+ * docs/protocol/alp-json-rpc-v1.md §12.4): a file of at most `maxReadBytes`, a listing of at most
35
+ * `maxListEntries` entries, a relative path of at most `maxPathLength` characters.
36
+ */
37
+ export const WORKSPACE_FILES = Object.freeze({ maxReadBytes: 1024 * 1024, maxListEntries: 1000, maxPathLength: 4096 });
38
+
39
+ /** Directories whose whole content is refused: Git's, ALP's own configuration (its library overrides hold env values), and key and cloud-credential stores. */
40
+ const BLOCKED_DIRECTORIES = new Set(['.git', '.alp', '.ssh', '.gnupg', '.aws', '.azure', '.kube', '.docker', 'gcloud']);
41
+ /** Files that hold credentials by convention. */
42
+ const BLOCKED_NAMES = new Set(['.netrc', '_netrc', '.npmrc', '.yarnrc.yml', '.pypirc', '.pgpass', '.htpasswd', '.git-credentials', '.dockercfg', '.boto', '.s3cfg', '.mcp.json', 'auth.json', 'kubeconfig', 'settings.local.json', '.vault-token', 'application_default_credentials.json', 'token.json']);
43
+ /** Credential files known by their directory too: GitHub CLI's token file. */
44
+ const BLOCKED_PAIRS = new Set(['gh/hosts.yml']);
45
+ /** Names of credential files before their first dot (a leading dot aside): credentials.json, .credentials.json, secrets.yaml, a secrets/ directory. */
46
+ const BLOCKED_STEMS = new Set(['credential', 'credentials', 'secret', 'secrets']);
47
+ /**
48
+ * Source code is not a credential store: src/credentials.ts, lib/secrets.py stay readable (the stem
49
+ * rule only; never a data or config format such as json, yaml, toml, ini, xml, txt). alpd's
50
+ * private-key check still applies to them.
51
+ */
52
+ const SOURCE_EXTENSIONS = new Set(['ts', 'tsx', 'mts', 'cts', 'js', 'jsx', 'mjs', 'cjs', 'py', 'go', 'rs', 'java', 'kt', 'kts', 'rb', 'php', 'cs', 'swift', 'c', 'cc', 'cpp', 'h', 'hpp', 'm', 'mm', 'scala', 'dart', 'vue', 'svelte']);
53
+ /** Key, certificate-store, keyring and Terraform-state extensions. */
54
+ const BLOCKED_EXTENSIONS = new Set(['pem', 'key', 'p12', 'pfx', 'p8', 'jks', 'keystore', 'ppk', 'der', 'gpg', 'pgp', 'asc', 'kdbx', 'ovpn', 'tfstate', 'tfvars']);
55
+ /** .env and every name starting with it (.env.local, .envrc); id_* (SSH keys: id_rsa, id_ed25519.pub, id_work); service-account keys; shell and REPL histories; Terraform state backups. */
56
+ const BLOCKED_PATTERN = /^(?:\.env|id_|service[-_]?account)|_history$|\.tfstate\.backup$/;
57
+
58
+ /**
59
+ * Whether a path relative to a workspace names something workspace.files.list and
60
+ * workspace.files.read never show, whatever the grant: a segment that is a blocked directory, or
61
+ * any segment (file or directory) named as a secret is (BLOCKED_* above). Compared without case,
62
+ * as macOS and Windows file systems name files. alpd and the gateway both check it; alpd also
63
+ * refuses a file whose content holds a private key, whatever its name.
64
+ * @param {string} relative a path relative to the workspace, '/'-separated
65
+ */
66
+ export function blockedFilePath(relative) {
67
+ let previous = '';
68
+ for (const segment of String(relative).toLowerCase().split(/[\\/]/)) {
69
+ if (!segment) continue;
70
+ if (BLOCKED_PAIRS.has(`${previous}/${segment}`)) return true;
71
+ previous = segment;
72
+ if (BLOCKED_DIRECTORIES.has(segment) || BLOCKED_NAMES.has(segment) || BLOCKED_PATTERN.test(segment)) return true;
73
+ const dot = segment.lastIndexOf('.');
74
+ const extension = dot >= 0 ? segment.slice(dot + 1) : '';
75
+ if (BLOCKED_EXTENSIONS.has(extension)) return true;
76
+ // Any dotted part of the name but its extension: secrets.yaml, prod.secrets.yaml, .credentials.json.
77
+ const parts = segment.replace(/^\.+/, '').split('.');
78
+ if (parts.length > 1) parts.pop();
79
+ if (parts.some(part => BLOCKED_STEMS.has(part)) && !SOURCE_EXTENSIONS.has(extension)) return true;
80
+ }
81
+ return false;
82
+ }
22
83
 
23
84
  /** UTF-8 socket writers use one newline-terminated JSON value per frame. @param {unknown} value */
24
85
  export function encodeFrame(value) { return `${JSON.stringify(value)}\n`; }