@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/dist/rpc.d.ts CHANGED
@@ -31,9 +31,9 @@ export type EventNotification = {
31
31
  params: Envelope;
32
32
  };
33
33
  /** Names a daemon may list in its daemon.hello result (schema/methods-v1.json `capabilities`). */
34
- export type Capability = 'attach.after' | 'prompt.receipts' | 'extension.methods' | 'team' | 'task' | 'library' | 'settings.language';
34
+ export type Capability = 'attach.after' | 'prompt.receipts' | 'extension.methods' | 'team' | 'task' | 'library' | 'settings.language' | 'session.list.page' | 'question.watch' | 'question.watch.turns' | 'workspace.files' | 'workspace.files.read' | 'prompt.images';
35
35
  /** `data.reason` values alpd sets on errors (docs/protocol/alp-json-rpc-v1.md §5.3). */
36
- export type ErrorReason = 'hello_required' | 'protocol_version' | 'invalid_params' | 'invalid_id' | 'batch_unsupported' | 'frame_too_large' | 'response_too_large' | 'persistence_failed' | 'outcome_unknown' | 'key_conflict' | 'invalid_path' | 'invalid_scope' | 'invalid_entry' | 'secret_marker' | 'not_alp_project' | 'unknown_task' | 'not_found' | 'unsafe_path' | 'revision_conflict' | 'task_state' | 'in_use' | 'busy' | 'unreadable';
36
+ export type ErrorReason = 'hello_required' | 'protocol_version' | 'invalid_params' | 'invalid_id' | 'batch_unsupported' | 'frame_too_large' | 'response_too_large' | 'persistence_failed' | 'outcome_unknown' | 'key_conflict' | 'invalid_path' | 'invalid_scope' | 'invalid_entry' | 'secret_marker' | 'not_alp_project' | 'unknown_task' | 'not_found' | 'unsafe_path' | 'revision_conflict' | 'task_state' | 'in_use' | 'busy' | 'unreadable' | 'not_directory' | 'blocked_path' | 'ignored' | 'linked' | 'binary' | 'too_large';
37
37
  type Empty = Record<string, never>;
38
38
  /**
39
39
  * Params and result of every v1 method, as schema/methods-v1.json defines them; the protocol tests
@@ -53,7 +53,8 @@ export type RpcMethodMap = {
53
53
  result: {
54
54
  protocolVersion: 1;
55
55
  daemonVersion?: string;
56
- pid?: number;
56
+ pid?: number; /** The daemon host's machine name (os.hostname()), a label for clients. */
57
+ hostname?: string;
57
58
  capabilities?: string[];
58
59
  };
59
60
  };
@@ -147,6 +148,62 @@ export type RpcMethodMap = {
147
148
  questions: UserQuestion[];
148
149
  };
149
150
  };
151
+ /** Capability `question.watch`: the waiting questions now; then this connection is told of every tree's `question` and `question.resolved` (of `projectRoot` when given). `turns` (capability `question.watch.turns`): also every root's `turn.ended`. */
152
+ 'question.watch': {
153
+ params: {
154
+ projectRoot?: string;
155
+ turns?: boolean;
156
+ };
157
+ result: {
158
+ questions: UserQuestion[];
159
+ };
160
+ };
161
+ /** Capability `workspace.files`: files under `projectRoot` whose relative path matches `query`, best first; read-only, links not followed. */
162
+ 'workspace.files.search': {
163
+ params: {
164
+ projectRoot: string;
165
+ query?: string;
166
+ limit?: number;
167
+ };
168
+ result: {
169
+ files: Array<{
170
+ path: string;
171
+ }>;
172
+ truncated?: boolean;
173
+ };
174
+ };
175
+ /** Capability `workspace.files.read`: the entries of a directory under `projectRoot` (its root when `path` is left out); links, blocked and (unless `includeIgnored`) .gitignore'd entries left out. */
176
+ 'workspace.files.list': {
177
+ params: {
178
+ projectRoot: string;
179
+ path?: string;
180
+ includeIgnored?: boolean;
181
+ };
182
+ result: {
183
+ entries: Array<{
184
+ name: string;
185
+ path: string;
186
+ kind: 'file' | 'directory';
187
+ size?: number;
188
+ modifiedAt?: string;
189
+ }>;
190
+ truncated?: boolean;
191
+ };
192
+ };
193
+ /** Capability `workspace.files.read`: a UTF-8 text file under `projectRoot`, at most 1 MiB; read-only, links not followed. */
194
+ 'workspace.files.read': {
195
+ params: {
196
+ projectRoot: string;
197
+ path: string;
198
+ includeIgnored?: boolean;
199
+ };
200
+ result: {
201
+ path: string;
202
+ content: string;
203
+ size: number;
204
+ modifiedAt?: string;
205
+ };
206
+ };
150
207
  'question.answer': {
151
208
  params: {
152
209
  questionId: string;
@@ -263,15 +320,21 @@ export type RpcMethodMap = {
263
320
  session: SessionSummary;
264
321
  };
265
322
  };
323
+ /** `limit`, `cursor`, `query` and `projectRoots` with capability `session.list.page`: a page, and `nextCursor` while more follow. */
266
324
  'session.list': {
267
325
  params: {
268
326
  projectRoot?: string;
327
+ projectRoots?: string[];
269
328
  rootsOnly?: boolean;
270
329
  includeClosed?: boolean;
271
330
  includeArchived?: boolean;
331
+ limit?: number;
332
+ cursor?: string;
333
+ query?: string;
272
334
  };
273
335
  result: {
274
336
  sessions: SessionSummary[];
337
+ nextCursor?: string;
275
338
  };
276
339
  };
277
340
  'session.status': {
package/dist/session.d.ts CHANGED
@@ -53,9 +53,20 @@ export type OpenOptions = {
53
53
  /** False when the client cannot show child sessions; delegation is then refused in this tree. */
54
54
  delegation?: boolean;
55
55
  };
56
+ /** An image inline in a prompt (capability `prompt.images`): base64 data, never a path or URL; limits in PROMPT_IMAGES. */
57
+ export type PromptImage = {
58
+ type: 'image';
59
+ mimeType: 'image/png' | 'image/jpeg' | 'image/gif' | 'image/webp';
60
+ data: string;
61
+ name?: string;
62
+ };
63
+ /** Text parts carry `text`; image parts (`type: 'image'`) carry PromptImage's fields. */
56
64
  export type PromptContent = Array<{
57
65
  type: string;
58
66
  text?: string;
67
+ mimeType?: string;
68
+ data?: string;
69
+ name?: string;
59
70
  }>;
60
71
  export type PromptInput = {
61
72
  clientMessageId: string;
package/dist/validate.js CHANGED
@@ -95,6 +95,10 @@ function check(schema, value, at, root, issues) {
95
95
  if (schema.maxLength !== void 0 && length > schema.maxLength) issues.push({ path: at, message: `must have at most ${schema.maxLength} characters` });
96
96
  if (schema.pattern !== void 0 && !new RegExp(schema.pattern, "u").test(text)) issues.push({ path: at, message: `must match ${schema.pattern}` });
97
97
  }
98
+ if (kind === "array" && schema.minItems !== void 0 && /** @type {unknown[]} */
99
+ value.length < schema.minItems) issues.push({ path: at, message: `must have at least ${schema.minItems} items` });
100
+ if (kind === "array" && schema.maxItems !== void 0 && /** @type {unknown[]} */
101
+ value.length > schema.maxItems) issues.push({ path: at, message: `must have at most ${schema.maxItems} items` });
98
102
  if (kind === "array" && schema.items !== void 0) {
99
103
  const array = (
100
104
  /** @type {unknown[]} */
package/dist/wire.d.ts CHANGED
@@ -1,3 +1,12 @@
1
+ /**
2
+ * Whether a path relative to a workspace names something workspace.files.list and
3
+ * workspace.files.read never show, whatever the grant: a segment that is a blocked directory, or
4
+ * any segment (file or directory) named as a secret is (BLOCKED_* above). Compared without case,
5
+ * as macOS and Windows file systems name files. alpd and the gateway both check it; alpd also
6
+ * refuses a file whose content holds a private key, whatever its name.
7
+ * @param {string} relative a path relative to the workspace, '/'-separated
8
+ */
9
+ export function blockedFilePath(relative: string): boolean;
1
10
  /** UTF-8 socket writers use one newline-terminated JSON value per frame. @param {unknown} value */
2
11
  export function encodeFrame(value: unknown): string;
3
12
  /** Parse one complete frame. Buffering and failure handling belong to the socket owner. @param {string} line @returns {unknown} */
@@ -37,5 +46,34 @@ export const CAPABILITY: Readonly<{
37
46
  task: "task";
38
47
  library: "library";
39
48
  settingsLanguage: "settings.language";
49
+ sessionListPage: "session.list.page";
50
+ questionWatch: "question.watch";
51
+ questionWatchTurns: "question.watch.turns";
52
+ workspaceFiles: "workspace.files";
53
+ workspaceFilesRead: "workspace.files.read";
54
+ promptImages: "prompt.images";
55
+ }>;
56
+ /**
57
+ * What alpd takes as images in one session.prompt (capability `prompt.images`): inline base64 of
58
+ * these types only, never a path or URL; at most `maxCount` of them, each at most `maxBytes`
59
+ * decoded, and `maxTotalBytes` together, so their base64 fits one MAX_FRAME. A gateway may allow
60
+ * less (docs/alp/remote-gateway.md).
61
+ */
62
+ export const PROMPT_IMAGES: Readonly<{
63
+ mimeTypes: readonly string[];
64
+ maxCount: 8;
65
+ maxBytes: number;
66
+ maxTotalBytes: number;
40
67
  }>;
68
+ export function base64Bytes(data: string): number;
41
69
  export const RPC_METHODS: readonly string[];
70
+ /**
71
+ * Limits of workspace.files.list and workspace.files.read (capability `workspace.files.read`,
72
+ * docs/protocol/alp-json-rpc-v1.md §12.4): a file of at most `maxReadBytes`, a listing of at most
73
+ * `maxListEntries` entries, a relative path of at most `maxPathLength` characters.
74
+ */
75
+ export const WORKSPACE_FILES: Readonly<{
76
+ maxReadBytes: number;
77
+ maxListEntries: 1000;
78
+ maxPathLength: 4096;
79
+ }>;
package/dist/wire.js CHANGED
@@ -10,8 +10,34 @@ var AlpRpcError = class extends Error {
10
10
  };
11
11
  var ERROR = Object.freeze({ failed: 1e3, notFound: 1001, forbidden: 1003, conflict: 1004, unavailable: 1005, protocol: 1006 });
12
12
  var RPC_ERROR = Object.freeze({ parseError: -32700, invalidRequest: -32600, methodNotFound: -32601, invalidParams: -32602 });
13
- var CAPABILITY = Object.freeze({ attachAfter: "attach.after", promptReceipts: "prompt.receipts", extensionMethods: "extension.methods", team: "team", task: "task", library: "library", settingsLanguage: "settings.language" });
14
- 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", "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"]);
13
+ 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" });
14
+ 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 });
15
+ var base64Bytes = (data) => Math.floor(data.length * 3 / 4) - (data.endsWith("==") ? 2 : data.endsWith("=") ? 1 : 0);
16
+ 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"]);
17
+ var WORKSPACE_FILES = Object.freeze({ maxReadBytes: 1024 * 1024, maxListEntries: 1e3, maxPathLength: 4096 });
18
+ var BLOCKED_DIRECTORIES = /* @__PURE__ */ new Set([".git", ".alp", ".ssh", ".gnupg", ".aws", ".azure", ".kube", ".docker", "gcloud"]);
19
+ var BLOCKED_NAMES = /* @__PURE__ */ 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"]);
20
+ var BLOCKED_PAIRS = /* @__PURE__ */ new Set(["gh/hosts.yml"]);
21
+ var BLOCKED_STEMS = /* @__PURE__ */ new Set(["credential", "credentials", "secret", "secrets"]);
22
+ var SOURCE_EXTENSIONS = /* @__PURE__ */ 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"]);
23
+ var BLOCKED_EXTENSIONS = /* @__PURE__ */ new Set(["pem", "key", "p12", "pfx", "p8", "jks", "keystore", "ppk", "der", "gpg", "pgp", "asc", "kdbx", "ovpn", "tfstate", "tfvars"]);
24
+ var BLOCKED_PATTERN = /^(?:\.env|id_|service[-_]?account)|_history$|\.tfstate\.backup$/;
25
+ function blockedFilePath(relative) {
26
+ let previous = "";
27
+ for (const segment of String(relative).toLowerCase().split(/[\\/]/)) {
28
+ if (!segment) continue;
29
+ if (BLOCKED_PAIRS.has(`${previous}/${segment}`)) return true;
30
+ previous = segment;
31
+ if (BLOCKED_DIRECTORIES.has(segment) || BLOCKED_NAMES.has(segment) || BLOCKED_PATTERN.test(segment)) return true;
32
+ const dot = segment.lastIndexOf(".");
33
+ const extension = dot >= 0 ? segment.slice(dot + 1) : "";
34
+ if (BLOCKED_EXTENSIONS.has(extension)) return true;
35
+ const parts = segment.replace(/^\.+/, "").split(".");
36
+ if (parts.length > 1) parts.pop();
37
+ if (parts.some((part) => BLOCKED_STEMS.has(part)) && !SOURCE_EXTENSIONS.has(extension)) return true;
38
+ }
39
+ return false;
40
+ }
15
41
  function encodeFrame(value) {
16
42
  return `${JSON.stringify(value)}
17
43
  `;
@@ -24,9 +50,13 @@ export {
24
50
  CAPABILITY,
25
51
  ERROR,
26
52
  MAX_FRAME,
53
+ PROMPT_IMAGES,
27
54
  PROTOCOL_VERSION,
28
55
  RPC_ERROR,
29
56
  RPC_METHODS,
57
+ WORKSPACE_FILES,
58
+ base64Bytes,
59
+ blockedFilePath,
30
60
  decodeFrame,
31
61
  encodeFrame
32
62
  };