@agentproto/computer-api 0.2.1 → 0.2.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.
package/README.md CHANGED
@@ -33,8 +33,8 @@ describe. A major bump of this package is where that constant changes.
33
33
  | POST | `/v1/computers/:id/exec` | `execRequestSchema` | `execResponseSchema` |
34
34
  | POST | `/v1/computers/:id/suspend` | - | `suspendResponseSchema` |
35
35
  | POST | `/v1/computers/:id/resume` | - | `resumeResponseSchema` |
36
- | POST | `/v1/computers/:id/attach` | - | `sessionsResponseSchema` |
37
- | POST | `/v1/computers/:id/detach` | - | `sessionsResponseSchema` |
36
+ | POST | `/v1/computers/:id/attach` | `attachRequestSchema` (optional body) | `sessionsResponseSchema` |
37
+ | POST | `/v1/computers/:id/detach` | `attachRequestSchema` (optional body) | `sessionsResponseSchema` |
38
38
  | DELETE | `/v1/computers/:id` | - (`?force=true` optional) | `suspendResponseSchema` shape (all-optional) |
39
39
  | any | error body | - | `errorResponseSchema` |
40
40
 
@@ -48,9 +48,10 @@ same schemas, so the test double cannot drift from the published contract.
48
48
  ## Notes
49
49
 
50
50
  - Sessions: `maxSessions` on create sizes the computer for that many
51
- concurrent sessions. `attach` takes a slot (409 at the cap), `detach`
52
- releases one (floors at 0), and `DELETE` answers 409 while sessions are
53
- attached unless `?force=true` is passed.
51
+ concurrent sessions. `attach` takes a slot (409 at the cap; an attach
52
+ by a `holder` that already holds a slot is idempotent: 200 with the
53
+ count unchanged), `detach` releases one (floors at 0), and `DELETE`
54
+ answers 409 while sessions are attached unless `?force=true` is passed.
54
55
  - `resume` MAY rename the computer: the controller can restore the suspend
55
56
  snapshot into a fresh computer under a NEW generated id and answer
56
57
  `{ id: <new>, state: "running" }`. Callers must treat the old id as gone
package/dist/index.d.ts CHANGED
@@ -153,11 +153,26 @@ declare const resumeResponseSchema: z.ZodObject<{
153
153
  state: z.ZodOptional<z.ZodString>;
154
154
  }, z.core.$loose>;
155
155
  type ResumeResponse = z.infer<typeof resumeResponseSchema>;
156
+ /**
157
+ * Optional request body of `POST /v1/computers/:id/attach` and `.../detach`:
158
+ * a stable holder identity (the agentproto session id of the daemon taking
159
+ * or releasing the slot). A controller that knows the holder can make
160
+ * re-attach idempotent (a restarted daemon re-attaches onto the slot its
161
+ * dead predecessor still held instead of hitting the 409 session limit) and
162
+ * scope the detach to that holder's slot. Absent on both routes: a body-less
163
+ * attach/detach keeps the legacy anonymous counting. `detach` ignores the
164
+ * holder of an unknown slot (no-op for that holder).
165
+ */
166
+ declare const attachRequestSchema: z.ZodObject<{
167
+ holder: z.ZodOptional<z.ZodString>;
168
+ }, z.core.$loose>;
169
+ type AttachRequest = z.infer<typeof attachRequestSchema>;
156
170
  /**
157
171
  * 200 response body of `POST /v1/computers/:id/attach` and `.../detach`:
158
172
  * the controller-held session count after the call and the cap the computer
159
- * was sized for. Neither route takes a body. Attach answers 409 when the
160
- * computer already hosts `maxSessions` sessions; detach floors at 0.
173
+ * was sized for. Attach answers 409 when the computer already hosts
174
+ * `maxSessions` sessions (an attach by a holder that already holds a slot
175
+ * is idempotent: 200 with the count unchanged); detach floors at 0.
161
176
  * `DELETE /v1/computers/:id` answers 409 while sessions are attached unless
162
177
  * the `force=true` query parameter is passed.
163
178
  */
@@ -167,4 +182,4 @@ declare const sessionsResponseSchema: z.ZodObject<{
167
182
  }, z.core.$loose>;
168
183
  type SessionsResponse = z.infer<typeof sessionsResponseSchema>;
169
184
 
170
- export { COMPUTER_API_VERSION, COMPUTER_STATES, COMPUTER_STATE_UNKNOWN, type ComputerState, type ComputerStatus, type CreateComputerRequest, type CreateComputerResponse, type EgressRule, type ErrorResponse, type ExecRequest, type ExecResponse, type PortPublish, type ResumeResponse, type SessionsResponse, type SuspendResponse, computerStateSchema, computerStatusSchema, createComputerRequestSchema, createComputerResponseSchema, egressRuleSchema, errorResponseSchema, execRequestSchema, execResponseSchema, portPublishSchema, resumeResponseSchema, sessionsResponseSchema, suspendResponseSchema };
185
+ export { type AttachRequest, COMPUTER_API_VERSION, COMPUTER_STATES, COMPUTER_STATE_UNKNOWN, type ComputerState, type ComputerStatus, type CreateComputerRequest, type CreateComputerResponse, type EgressRule, type ErrorResponse, type ExecRequest, type ExecResponse, type PortPublish, type ResumeResponse, type SessionsResponse, type SuspendResponse, attachRequestSchema, computerStateSchema, computerStatusSchema, createComputerRequestSchema, createComputerResponseSchema, egressRuleSchema, errorResponseSchema, execRequestSchema, execResponseSchema, portPublishSchema, resumeResponseSchema, sessionsResponseSchema, suspendResponseSchema };
package/dist/index.mjs CHANGED
@@ -84,11 +84,14 @@ var resumeResponseSchema = z.object({
84
84
  id: z.string().optional(),
85
85
  state: z.string().optional()
86
86
  }).passthrough();
87
+ var attachRequestSchema = z.object({
88
+ holder: z.string().min(1).max(128).optional()
89
+ }).passthrough();
87
90
  var sessionsResponseSchema = z.object({
88
91
  sessions: z.number().int().optional(),
89
92
  maxSessions: z.number().int().optional()
90
93
  }).passthrough();
91
94
 
92
- export { COMPUTER_API_VERSION, COMPUTER_STATES, COMPUTER_STATE_UNKNOWN, computerStateSchema, computerStatusSchema, createComputerRequestSchema, createComputerResponseSchema, egressRuleSchema, errorResponseSchema, execRequestSchema, execResponseSchema, portPublishSchema, resumeResponseSchema, sessionsResponseSchema, suspendResponseSchema };
95
+ export { COMPUTER_API_VERSION, COMPUTER_STATES, COMPUTER_STATE_UNKNOWN, attachRequestSchema, computerStateSchema, computerStatusSchema, createComputerRequestSchema, createComputerResponseSchema, egressRuleSchema, errorResponseSchema, execRequestSchema, execResponseSchema, portPublishSchema, resumeResponseSchema, sessionsResponseSchema, suspendResponseSchema };
93
96
  //# sourceMappingURL=index.mjs.map
94
97
  //# sourceMappingURL=index.mjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/version.ts","../src/computer-states.ts","../src/schemas.ts"],"names":["z"],"mappings":"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","file":"index.mjs","sourcesContent":["/**\n * Version of the computer controller HTTP contract described by this\n * package. The controller routes live under this version prefix (for\n * example `POST /v1/computers`), and every payload schema here corresponds\n * to that version.\n *\n * Semver of the PACKAGE tracks this contract: a MAJOR bump of\n * `@agentproto/computer-api` is where this constant changes.\n */\nexport const COMPUTER_API_VERSION = \"v1\"\n","/**\n * Driver-level computer states, as reported by `GET /v1/computers/:id`.\n *\n * The controller itself never reports `suspended`: a suspended computer\n * reads back as `stopped`. The state is kept here as a tolerated alias so\n * a newer controller can report it without breaking an older client.\n */\nexport const COMPUTER_STATES = [\n \"running\",\n \"stopped\",\n \"paused\",\n \"crashed\",\n \"starting\",\n \"unknown\",\n \"missing\",\n \"suspended\",\n] as const\n\nexport type ComputerState = (typeof COMPUTER_STATES)[number]\n\nimport { z } from \"zod\"\n\n/**\n * The state field is READ-tolerant: any unknown future state is bucketed\n * under `\"unknown\"` instead of rejecting, so a status read never throws on\n * controller evolution.\n */\nexport const computerStateSchema = z.string().transform((v): ComputerState => {\n return (COMPUTER_STATES as readonly string[]).includes(v) ? (v as ComputerState) : \"unknown\"\n})\n\nexport const COMPUTER_STATE_UNKNOWN: ComputerState = \"unknown\"\n","/**\n * Zod schemas for the computer controller's HTTP contract constants -\n * the generic, deployment-independent shape of every endpoint:\n *\n * POST /v1/computers create\n * GET /v1/computers/:id status\n * POST /v1/computers/:id/exec synchronous exec of an argv array\n * POST /v1/computers/:id/suspend full snapshot + stop\n * POST /v1/computers/:id/resume restore into a FRESH computer\n * POST /v1/computers/:id/attach take a session slot\n * POST /v1/computers/:id/detach release a session slot\n * DELETE /v1/computers/:id rm + snapshot cleanup (?force=true skips the session check)\n *\n * These are deliberately MINIMAL: only the fields a client actually\n * consumes are declared, and every schema allows (and preserves) extra\n * fields so a controller version adding fields never breaks an older\n * client. Template ids are an OPEN string - any registered id is valid.\n *\n * Nothing deployment-specific belongs here: no host names, no IPs, no\n * tenant names, no auth realm details. Auth headers are wired by the\n * client, not the contract.\n */\n\nimport { z } from \"zod\"\nimport { computerStateSchema } from \"./computer-states.js\"\n\n// ── transport pieces ──────────────────────────────────────────────────\n\n/** A published port: the host-side port mapped to the guest-side port. */\nexport const portPublishSchema = z.object({\n host: z.number().int(),\n guest: z.number().int(),\n})\nexport type PortPublish = z.infer<typeof portPublishSchema>\n\n/** Egress allowlist entry in the create body: destination host + port. */\nexport const egressRuleSchema = z.object({\n host: z.string(),\n port: z.number().int(),\n})\nexport type EgressRule = z.infer<typeof egressRuleSchema>\n\n/**\n * Controller error bodies are always `{ error: string }`, sometimes with a\n * `detail` array of validation issues. The `code` strings a controller may\n * emit (for example unknown template, capacity) are contract-OPEN: clients\n * inspect but never switch exhaustively on them.\n */\nexport const errorResponseSchema = z.object({\n error: z.string(),\n detail: z.unknown().optional(),\n}).passthrough()\nexport type ErrorResponse = z.infer<typeof errorResponseSchema>\n\n// ── POST /v1/computers ────────────────────────────────────────────────\n\n/** Request body for `POST /v1/computers`. */\nexport const createComputerRequestSchema = z.object({\n /** Registered template id (OPEN string; a controller rejects unknown ids). */\n template: z.string(),\n /** Tenant the computer is created under (scope marker, not a name). */\n tenantId: z.string(),\n cpus: z.number().int().optional(),\n memoryMiB: z.number().int().optional(),\n rootDiskMiB: z.number().int().optional(),\n /** Computer TTL seconds. */\n ttl: z.number().int().optional(),\n /** Concurrent sessions the computer is sized for (controller default 1). */\n maxSessions: z.number().int().optional(),\n /** Environment variables injected into the computer. */\n env: z.record(z.string(), z.string()).optional(),\n /** Deny-by-default egress allowlist; a computer created without one has no DNS. */\n egress: z.object({ allow: z.array(egressRuleSchema) }).optional(),\n}).passthrough()\nexport type CreateComputerRequest = z.infer<typeof createComputerRequestSchema>\n\n/** 201 response body of `POST /v1/computers`. */\nexport const createComputerResponseSchema = z.object({\n id: z.string(),\n template: z.string(),\n state: z.string(),\n ports: z.array(portPublishSchema).optional(),\n}).passthrough()\nexport type CreateComputerResponse = z.infer<typeof createComputerResponseSchema>\n\n// ── GET /v1/computers/:id ─────────────────────────────────────────────\n\n/**\n * Status response of `GET /v1/computers/:id` (status + published ports and\n * any egress/template metadata; extras preserved). The state is parsed\n * READ-tOLERANTLY: unknown future states bucket under `\"unknown\"`.\n */\nexport const computerStatusSchema = z.object({\n name: z.string(),\n state: computerStateSchema,\n ports: z.array(portPublishSchema).optional(),\n template: z.string().optional(),\n tenantId: z.string().optional(),\n}).passthrough()\nexport type ComputerStatus = z.infer<typeof computerStatusSchema>\n\n// ── POST /v1/computers/:id/exec ───────────────────────────────────────\n\n/** Request body of `POST /v1/computers/:id/exec` - synchronous argv exec. */\nexport const execRequestSchema = z.object({\n argv: z.array(z.string()),\n /** Per-exec timeout; controllers cap this (for example at 600 seconds). */\n timeoutSec: z.number().int(),\n}).passthrough()\nexport type ExecRequest = z.infer<typeof execRequestSchema>\n\n/** 200 response body of `POST /v1/computers/:id/exec`. Controllers may\n * redact registered secret values from the streams and cap their length. */\nexport const execResponseSchema = z.object({\n exitCode: z.number().int(),\n stdout: z.string(),\n stderr: z.string(),\n}).passthrough()\nexport type ExecResponse = z.infer<typeof execResponseSchema>\n\n// ── POST /v1/computers/:id/suspend ────────────────────────────────────\n\n/** 200 response body of `POST /v1/computers/:id/suspend`. */\nexport const suspendResponseSchema = z.object({\n ok: z.boolean().optional(),\n snapshot: z.string().optional(),\n state: z.string().optional(),\n}).passthrough()\nexport type SuspendResponse = z.infer<typeof suspendResponseSchema>\n\n// ── POST /v1/computers/:id/resume ─────────────────────────────────────\n\n/**\n * Response of `POST /v1/computers/:id/resume`. MAY rename the computer:\n * the controller can restore the suspend snapshot into a FRESH computer\n * under a NEW generated id and answer `{ id: <new>, state: \"running\" }`.\n * Callers must treat the old id as gone and adopt the returned id.\n */\nexport const resumeResponseSchema = z.object({\n ok: z.boolean().optional(),\n id: z.string().optional(),\n state: z.string().optional(),\n}).passthrough()\nexport type ResumeResponse = z.infer<typeof resumeResponseSchema>\n\n// ── POST /v1/computers/:id/attach | detach ────────────────────────────\n\n/**\n * 200 response body of `POST /v1/computers/:id/attach` and `.../detach`:\n * the controller-held session count after the call and the cap the computer\n * was sized for. Neither route takes a body. Attach answers 409 when the\n * computer already hosts `maxSessions` sessions; detach floors at 0.\n * `DELETE /v1/computers/:id` answers 409 while sessions are attached unless\n * the `force=true` query parameter is passed.\n */\nexport const sessionsResponseSchema = z.object({\n sessions: z.number().int().optional(),\n maxSessions: z.number().int().optional(),\n}).passthrough()\nexport type SessionsResponse = z.infer<typeof sessionsResponseSchema>\n"]}
1
+ {"version":3,"sources":["../src/version.ts","../src/computer-states.ts","../src/schemas.ts"],"names":["z"],"mappings":"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","file":"index.mjs","sourcesContent":["/**\n * Version of the computer controller HTTP contract described by this\n * package. The controller routes live under this version prefix (for\n * example `POST /v1/computers`), and every payload schema here corresponds\n * to that version.\n *\n * Semver of the PACKAGE tracks this contract: a MAJOR bump of\n * `@agentproto/computer-api` is where this constant changes.\n */\nexport const COMPUTER_API_VERSION = \"v1\"\n","/**\n * Driver-level computer states, as reported by `GET /v1/computers/:id`.\n *\n * The controller itself never reports `suspended`: a suspended computer\n * reads back as `stopped`. The state is kept here as a tolerated alias so\n * a newer controller can report it without breaking an older client.\n */\nexport const COMPUTER_STATES = [\n \"running\",\n \"stopped\",\n \"paused\",\n \"crashed\",\n \"starting\",\n \"unknown\",\n \"missing\",\n \"suspended\",\n] as const\n\nexport type ComputerState = (typeof COMPUTER_STATES)[number]\n\nimport { z } from \"zod\"\n\n/**\n * The state field is READ-tolerant: any unknown future state is bucketed\n * under `\"unknown\"` instead of rejecting, so a status read never throws on\n * controller evolution.\n */\nexport const computerStateSchema = z.string().transform((v): ComputerState => {\n return (COMPUTER_STATES as readonly string[]).includes(v) ? (v as ComputerState) : \"unknown\"\n})\n\nexport const COMPUTER_STATE_UNKNOWN: ComputerState = \"unknown\"\n","/**\n * Zod schemas for the computer controller's HTTP contract constants -\n * the generic, deployment-independent shape of every endpoint:\n *\n * POST /v1/computers create\n * GET /v1/computers/:id status\n * POST /v1/computers/:id/exec synchronous exec of an argv array\n * POST /v1/computers/:id/suspend full snapshot + stop\n * POST /v1/computers/:id/resume restore into a FRESH computer\n * POST /v1/computers/:id/attach take a session slot\n * POST /v1/computers/:id/detach release a session slot\n * DELETE /v1/computers/:id rm + snapshot cleanup (?force=true skips the session check)\n *\n * These are deliberately MINIMAL: only the fields a client actually\n * consumes are declared, and every schema allows (and preserves) extra\n * fields so a controller version adding fields never breaks an older\n * client. Template ids are an OPEN string - any registered id is valid.\n *\n * Nothing deployment-specific belongs here: no host names, no IPs, no\n * tenant names, no auth realm details. Auth headers are wired by the\n * client, not the contract.\n */\n\nimport { z } from \"zod\"\nimport { computerStateSchema } from \"./computer-states.js\"\n\n// ── transport pieces ──────────────────────────────────────────────────\n\n/** A published port: the host-side port mapped to the guest-side port. */\nexport const portPublishSchema = z.object({\n host: z.number().int(),\n guest: z.number().int(),\n})\nexport type PortPublish = z.infer<typeof portPublishSchema>\n\n/** Egress allowlist entry in the create body: destination host + port. */\nexport const egressRuleSchema = z.object({\n host: z.string(),\n port: z.number().int(),\n})\nexport type EgressRule = z.infer<typeof egressRuleSchema>\n\n/**\n * Controller error bodies are always `{ error: string }`, sometimes with a\n * `detail` array of validation issues. The `code` strings a controller may\n * emit (for example unknown template, capacity) are contract-OPEN: clients\n * inspect but never switch exhaustively on them.\n */\nexport const errorResponseSchema = z.object({\n error: z.string(),\n detail: z.unknown().optional(),\n}).passthrough()\nexport type ErrorResponse = z.infer<typeof errorResponseSchema>\n\n// ── POST /v1/computers ────────────────────────────────────────────────\n\n/** Request body for `POST /v1/computers`. */\nexport const createComputerRequestSchema = z.object({\n /** Registered template id (OPEN string; a controller rejects unknown ids). */\n template: z.string(),\n /** Tenant the computer is created under (scope marker, not a name). */\n tenantId: z.string(),\n cpus: z.number().int().optional(),\n memoryMiB: z.number().int().optional(),\n rootDiskMiB: z.number().int().optional(),\n /** Computer TTL seconds. */\n ttl: z.number().int().optional(),\n /** Concurrent sessions the computer is sized for (controller default 1). */\n maxSessions: z.number().int().optional(),\n /** Environment variables injected into the computer. */\n env: z.record(z.string(), z.string()).optional(),\n /** Deny-by-default egress allowlist; a computer created without one has no DNS. */\n egress: z.object({ allow: z.array(egressRuleSchema) }).optional(),\n}).passthrough()\nexport type CreateComputerRequest = z.infer<typeof createComputerRequestSchema>\n\n/** 201 response body of `POST /v1/computers`. */\nexport const createComputerResponseSchema = z.object({\n id: z.string(),\n template: z.string(),\n state: z.string(),\n ports: z.array(portPublishSchema).optional(),\n}).passthrough()\nexport type CreateComputerResponse = z.infer<typeof createComputerResponseSchema>\n\n// ── GET /v1/computers/:id ─────────────────────────────────────────────\n\n/**\n * Status response of `GET /v1/computers/:id` (status + published ports and\n * any egress/template metadata; extras preserved). The state is parsed\n * READ-tOLERANTLY: unknown future states bucket under `\"unknown\"`.\n */\nexport const computerStatusSchema = z.object({\n name: z.string(),\n state: computerStateSchema,\n ports: z.array(portPublishSchema).optional(),\n template: z.string().optional(),\n tenantId: z.string().optional(),\n}).passthrough()\nexport type ComputerStatus = z.infer<typeof computerStatusSchema>\n\n// ── POST /v1/computers/:id/exec ───────────────────────────────────────\n\n/** Request body of `POST /v1/computers/:id/exec` - synchronous argv exec. */\nexport const execRequestSchema = z.object({\n argv: z.array(z.string()),\n /** Per-exec timeout; controllers cap this (for example at 600 seconds). */\n timeoutSec: z.number().int(),\n}).passthrough()\nexport type ExecRequest = z.infer<typeof execRequestSchema>\n\n/** 200 response body of `POST /v1/computers/:id/exec`. Controllers may\n * redact registered secret values from the streams and cap their length. */\nexport const execResponseSchema = z.object({\n exitCode: z.number().int(),\n stdout: z.string(),\n stderr: z.string(),\n}).passthrough()\nexport type ExecResponse = z.infer<typeof execResponseSchema>\n\n// ── POST /v1/computers/:id/suspend ────────────────────────────────────\n\n/** 200 response body of `POST /v1/computers/:id/suspend`. */\nexport const suspendResponseSchema = z.object({\n ok: z.boolean().optional(),\n snapshot: z.string().optional(),\n state: z.string().optional(),\n}).passthrough()\nexport type SuspendResponse = z.infer<typeof suspendResponseSchema>\n\n// ── POST /v1/computers/:id/resume ─────────────────────────────────────\n\n/**\n * Response of `POST /v1/computers/:id/resume`. MAY rename the computer:\n * the controller can restore the suspend snapshot into a FRESH computer\n * under a NEW generated id and answer `{ id: <new>, state: \"running\" }`.\n * Callers must treat the old id as gone and adopt the returned id.\n */\nexport const resumeResponseSchema = z.object({\n ok: z.boolean().optional(),\n id: z.string().optional(),\n state: z.string().optional(),\n}).passthrough()\nexport type ResumeResponse = z.infer<typeof resumeResponseSchema>\n\n// ── POST /v1/computers/:id/attach | detach ────────────────────────────\n\n/**\n * Optional request body of `POST /v1/computers/:id/attach` and `.../detach`:\n * a stable holder identity (the agentproto session id of the daemon taking\n * or releasing the slot). A controller that knows the holder can make\n * re-attach idempotent (a restarted daemon re-attaches onto the slot its\n * dead predecessor still held instead of hitting the 409 session limit) and\n * scope the detach to that holder's slot. Absent on both routes: a body-less\n * attach/detach keeps the legacy anonymous counting. `detach` ignores the\n * holder of an unknown slot (no-op for that holder).\n */\nexport const attachRequestSchema = z.object({\n holder: z.string().min(1).max(128).optional(),\n}).passthrough()\nexport type AttachRequest = z.infer<typeof attachRequestSchema>\n\n/**\n * 200 response body of `POST /v1/computers/:id/attach` and `.../detach`:\n * the controller-held session count after the call and the cap the computer\n * was sized for. Attach answers 409 when the computer already hosts\n * `maxSessions` sessions (an attach by a holder that already holds a slot\n * is idempotent: 200 with the count unchanged); detach floors at 0.\n * `DELETE /v1/computers/:id` answers 409 while sessions are attached unless\n * the `force=true` query parameter is passed.\n */\nexport const sessionsResponseSchema = z.object({\n sessions: z.number().int().optional(),\n maxSessions: z.number().int().optional(),\n}).passthrough()\nexport type SessionsResponse = z.infer<typeof sessionsResponseSchema>\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agentproto/computer-api",
3
- "version": "0.2.1",
3
+ "version": "0.2.3",
4
4
  "description": "@agentproto/computer-api — versioned public contract for the computer controller HTTP API: zod schemas and inferred types for create/get/exec/suspend/resume/rm of computers, published ports, status and the error envelope.",
5
5
  "keywords": [
6
6
  "agentproto",