From 0e5358f83db9ef527776b793f2098ef9f749e879 Mon Sep 17 00:00:00 2001 From: colafornia Date: Wed, 11 Nov 2026 00:30:09 +0800 Subject: [PATCH] fix(workspace): preserve structured permission refusals Return typed pre-execution permission refusals from JSON file methods and the process service. Preserve requested permissions, optional command and refusal reason in the service result schemas and generated SDK. Reuse generic JSON and operation receipt consumers. Generated-by: Codex --- packages/plugin-sdk/src/contracts.ts | 2703 ++++++++++++++--- platform/computer-use/src/desktop/worker.rs | 9 +- plugins/agent/authority/src/permissions.rs | 60 +- plugins/agent/facts/src/tools.rs | 26 + plugins/agent/native/src/owner/auxiliary.rs | 5 +- plugins/agent/native/src/owner/queue.rs | 1 + plugins/workspace/README.md | 2 + plugins/workspace/README.zh-CN.md | 2 + plugins/workspace/api/src/files.rs | 5 + plugins/workspace/api/src/lib.rs | 6 + plugins/workspace/api/src/processes.rs | 13 +- .../workspace-files/native/src/lib.rs | 5 + .../workspace-files/native/src/permissions.rs | 16 +- .../workspace-processes/native/src/execute.rs | 12 +- .../workspace-processes/native/src/lib.rs | 4 + 15 files changed, 2418 insertions(+), 451 deletions(-) diff --git a/packages/plugin-sdk/src/contracts.ts b/packages/plugin-sdk/src/contracts.ts index 5a463cddaef..ab2361d10d2 100644 --- a/packages/plugin-sdk/src/contracts.ts +++ b/packages/plugin-sdk/src/contracts.ts @@ -42288,6 +42288,23 @@ export const contracts: { }, output: { oneOf: [ + { + type: 'object', + additionalProperties: false, + properties: { + kind: { + type: 'string', + const: 'permission_denied', + }, + executionState: { + $ref: '#/$defs/NotStarted', + }, + denial: { + $ref: '#/$defs/PermissionDenial', + }, + }, + required: ['kind', 'executionState', 'denial'], + }, { type: 'object', additionalProperties: false, @@ -42319,146 +42336,200 @@ export const contracts: { ], title: 'WorkspaceFileResult', $schema: 'https://json-schema.org/draft/2020-12/schema', - }, - resources: {}, - }, - edit: { - mode: 'mutation', - input: { - type: 'object', - additionalProperties: false, - properties: { - path: { + $defs: { + NotStarted: { type: 'string', - minLength: 1, + enum: ['not_started'], }, - old_string: { - type: 'string', - minLength: 1, + PermissionDenial: { + type: 'object', + additionalProperties: false, + properties: { + reason: { + $ref: '#/$defs/PermissionDenialReason', + }, + permissions: { + $ref: '#/$defs/Permissions', + }, + command: { + anyOf: [ + { + $ref: '#/$defs/PermissionCommand', + }, + { + type: 'null', + }, + ], + }, + message: { + type: 'string', + }, + }, + required: ['reason', 'permissions', 'message'], + description: + 'A Host decision made before the requested effect starts. Never inferred from stderr.', }, - new_string: { + PermissionDenialReason: { type: 'string', + enum: [ + 'requests_disabled', + 'user_denied', + 'protected_boundary', + 'approval_insufficient', + 'grant_expired', + ], }, - }, - required: ['path', 'old_string', 'new_string'], - title: 'EditInput', - $schema: 'https://json-schema.org/draft/2020-12/schema', - }, - output: { - oneOf: [ - { + Permissions: { type: 'object', additionalProperties: false, properties: { - kind: { - type: 'string', - const: 'completed', + filesystem: { + type: 'array', + items: { + $ref: '#/$defs/Rule', + }, + }, + network: { + $ref: '#/$defs/Network', }, - value: true, }, - required: ['kind', 'value'], + required: ['filesystem', 'network'], }, - { + Rule: { type: 'object', additionalProperties: false, properties: { - kind: { + path: { type: 'string', - const: 'failed', }, - message: { - type: 'string', + scope: { + $ref: '#/$defs/Scope', + }, + access: { + $ref: '#/$defs/Access', }, }, - required: ['kind', 'message'], + required: ['path', 'scope', 'access'], description: - 'A known completed failure. Uncertain external effects use the original receipt.', + 'Paths have executor-native meaning. Validate and materialize them on the\nexecution host, never on a remote Desktop. This document alone grants nothing.', }, - ], - title: 'WorkspaceFileResult', - $schema: 'https://json-schema.org/draft/2020-12/schema', - }, - resources: {}, - }, - glob: { - mode: 'query', - input: { - type: 'object', - additionalProperties: false, - properties: { - pattern: { + Scope: { type: 'string', - minLength: 1, + enum: ['subtree', 'exact'], }, - cwd: { + Access: { type: 'string', - minLength: 1, - default: '.', + enum: ['read', 'write', 'deny'], + description: + 'Equal-path conflict priority is different from authority breadth: deny wins.', }, - }, - required: ['pattern'], - title: 'Input', - $schema: 'https://json-schema.org/draft/2020-12/schema', - }, - output: { - oneOf: [ - { + Network: { + oneOf: [ + { + type: 'string', + enum: ['denied', 'allowed'], + }, + { + type: 'object', + properties: { + restricted: { + type: 'object', + properties: { + destinations: { + type: 'array', + uniqueItems: true, + items: { + $ref: '#/$defs/Destination', + }, + }, + }, + additionalProperties: false, + required: ['destinations'], + }, + }, + required: ['restricted'], + additionalProperties: false, + }, + ], + }, + Destination: { type: 'object', additionalProperties: false, properties: { - kind: { + host: { type: 'string', - const: 'completed', + minLength: 1, + maxLength: 253, + }, + port: { + type: 'integer', + format: 'uint16', + minimum: 1, + maximum: 65535, }, - value: true, }, - required: ['kind', 'value'], + required: ['host', 'port'], + description: + 'Destination authority, independent of the upstream proxy used to reach it.\nA domain grant does not authorize its subdomains or other ports.', }, - { + PermissionCommand: { type: 'object', additionalProperties: false, properties: { - kind: { + command: { type: 'string', - const: 'failed', }, - message: { + cwd: { type: 'string', }, }, - required: ['kind', 'message'], - description: - 'A known completed failure. Uncertain external effects use the original receipt.', + required: ['command', 'cwd'], }, - ], - title: 'WorkspaceFileResult', - $schema: 'https://json-schema.org/draft/2020-12/schema', + }, }, resources: {}, }, - grep: { - mode: 'query', + edit: { + mode: 'mutation', input: { type: 'object', additionalProperties: false, properties: { - pattern: { + path: { type: 'string', + minLength: 1, }, - path: { + old_string: { type: 'string', - default: '.', + minLength: 1, }, - glob: { + new_string: { type: 'string', }, }, - required: ['pattern'], - title: 'Input', + required: ['path', 'old_string', 'new_string'], + title: 'EditInput', $schema: 'https://json-schema.org/draft/2020-12/schema', }, output: { oneOf: [ + { + type: 'object', + additionalProperties: false, + properties: { + kind: { + type: 'string', + const: 'permission_denied', + }, + executionState: { + $ref: '#/$defs/NotStarted', + }, + denial: { + $ref: '#/$defs/PermissionDenial', + }, + }, + required: ['kind', 'executionState', 'denial'], + }, { type: 'object', additionalProperties: false, @@ -42490,147 +42561,177 @@ export const contracts: { ], title: 'WorkspaceFileResult', $schema: 'https://json-schema.org/draft/2020-12/schema', - }, - resources: {}, - }, - publishChange: { - mode: 'mutation', - input: { - type: 'object', - additionalProperties: false, - properties: { - publication: { - $ref: '#/$defs/ExternalPublicationId', - }, - index: { - type: 'integer', - format: 'uint64', - minimum: 0, - }, - }, - required: ['publication', 'index'], - title: 'PublishChange', - $schema: 'https://json-schema.org/draft/2020-12/schema', $defs: { - ExternalPublicationId: { + NotStarted: { type: 'string', - format: 'uuid', - }, - }, - }, - output: { - oneOf: [ - { - type: 'object', - properties: { - kind: { - type: 'string', - const: 'applied', - }, - }, - required: ['kind'], - additionalProperties: false, + enum: ['not_started'], }, - { + PermissionDenial: { type: 'object', additionalProperties: false, properties: { - kind: { - type: 'string', - const: 'conflict', - }, reason: { - $ref: '#/$defs/PublicationConflict', + $ref: '#/$defs/PermissionDenialReason', }, - }, - required: ['kind', 'reason'], + permissions: { + $ref: '#/$defs/Permissions', + }, + command: { + anyOf: [ + { + $ref: '#/$defs/PermissionCommand', + }, + { + type: 'null', + }, + ], + }, + message: { + type: 'string', + }, + }, + required: ['reason', 'permissions', 'message'], + description: + 'A Host decision made before the requested effect starts. Never inferred from stderr.', }, - { + PermissionDenialReason: { + type: 'string', + enum: [ + 'requests_disabled', + 'user_denied', + 'protected_boundary', + 'approval_insufficient', + 'grant_expired', + ], + }, + Permissions: { type: 'object', additionalProperties: false, properties: { - kind: { - type: 'string', - const: 'unsupported', + filesystem: { + type: 'array', + items: { + $ref: '#/$defs/Rule', + }, }, - reason: { - $ref: '#/$defs/PublicationUnsupported', + network: { + $ref: '#/$defs/Network', }, }, - required: ['kind', 'reason'], + required: ['filesystem', 'network'], }, - { + Rule: { type: 'object', additionalProperties: false, properties: { - kind: { + path: { type: 'string', - const: 'not_attempted', }, - reason: { - $ref: '#/$defs/PublicationNotAttempted', + scope: { + $ref: '#/$defs/Scope', + }, + access: { + $ref: '#/$defs/Access', }, }, - required: ['kind', 'reason'], + required: ['path', 'scope', 'access'], + description: + 'Paths have executor-native meaning. Validate and materialize them on the\nexecution host, never on a remote Desktop. This document alone grants nothing.', }, - ], - title: 'PublicationResult', - $schema: 'https://json-schema.org/draft/2020-12/schema', - $defs: { - PublicationConflict: { + Scope: { type: 'string', - enum: ['workspace_identity', 'entry_type', 'mode', 'content', 'entry_visibility'], + enum: ['subtree', 'exact'], }, - PublicationUnsupported: { + Access: { type: 'string', - enum: [ - 'structural_change', - 'uncaptured_workspace', - 'mode_platform', - 'aliased_target', + enum: ['read', 'write', 'deny'], + description: + 'Equal-path conflict priority is different from authority breadth: deny wins.', + }, + Network: { + oneOf: [ + { + type: 'string', + enum: ['denied', 'allowed'], + }, + { + type: 'object', + properties: { + restricted: { + type: 'object', + properties: { + destinations: { + type: 'array', + uniqueItems: true, + items: { + $ref: '#/$defs/Destination', + }, + }, + }, + additionalProperties: false, + required: ['destinations'], + }, + }, + required: ['restricted'], + additionalProperties: false, + }, ], }, - PublicationNotAttempted: { - type: 'string', - enum: ['cancelled'], + Destination: { + type: 'object', + additionalProperties: false, + properties: { + host: { + type: 'string', + minLength: 1, + maxLength: 253, + }, + port: { + type: 'integer', + format: 'uint16', + minimum: 1, + maximum: 65535, + }, + }, + required: ['host', 'port'], + description: + 'Destination authority, independent of the upstream proxy used to reach it.\nA domain grant does not authorize its subdomains or other ports.', }, - }, - }, - resources: { - content: { - type: { - name: 'maka.bytes', - version: 1, + PermissionCommand: { + type: 'object', + additionalProperties: false, + properties: { + command: { + type: 'string', + }, + cwd: { + type: 'string', + }, + }, + required: ['command', 'cwd'], }, - optional: true, }, }, + resources: {}, }, - readImage: { - mode: 'subscription', + glob: { + mode: 'query', input: { type: 'object', additionalProperties: false, properties: { - path: { + pattern: { type: 'string', minLength: 1, }, - offset: { - type: 'integer', - format: 'uint', - minimum: 0, - maximum: 9007199254740991, - }, - limit: { - type: 'integer', - format: 'uint', - minimum: 1, - maximum: 9007199254740991, + cwd: { + type: 'string', + minLength: 1, + default: '.', }, }, - required: ['path'], - title: 'ReadInput', + required: ['pattern'], + title: 'Input', $schema: 'https://json-schema.org/draft/2020-12/schema', }, output: { @@ -42641,31 +42742,28 @@ export const contracts: { properties: { kind: { type: 'string', - const: 'chunk', - }, - mimeType: { - type: 'string', - pattern: '^image/(png|jpeg|gif|webp)$', + const: 'permission_denied', }, - totalBytes: { - type: 'integer', - format: 'uint', - minimum: 1, - maximum: 9007199254740991, + executionState: { + $ref: '#/$defs/NotStarted', }, - offset: { - type: 'integer', - format: 'uint', - minimum: 0, - maximum: 9007199254740991, + denial: { + $ref: '#/$defs/PermissionDenial', }, - data: { + }, + required: ['kind', 'executionState', 'denial'], + }, + { + type: 'object', + additionalProperties: false, + properties: { + kind: { type: 'string', - minLength: 1, - maxLength: 43692, + const: 'completed', }, + value: true, }, - required: ['kind', 'mimeType', 'totalBytes', 'offset', 'data'], + required: ['kind', 'value'], }, { type: 'object', @@ -42673,64 +42771,1485 @@ export const contracts: { properties: { kind: { type: 'string', - const: 'complete', + const: 'failed', }, - detail: { + message: { + type: 'string', + }, + }, + required: ['kind', 'message'], + description: + 'A known completed failure. Uncertain external effects use the original receipt.', + }, + ], + title: 'WorkspaceFileResult', + $schema: 'https://json-schema.org/draft/2020-12/schema', + $defs: { + NotStarted: { + type: 'string', + enum: ['not_started'], + }, + PermissionDenial: { + type: 'object', + additionalProperties: false, + properties: { + reason: { + $ref: '#/$defs/PermissionDenialReason', + }, + permissions: { + $ref: '#/$defs/Permissions', + }, + command: { anyOf: [ { - $ref: '#/$defs/ImageDetail', + $ref: '#/$defs/PermissionCommand', }, { type: 'null', }, ], }, + message: { + type: 'string', + }, }, - required: ['kind'], - }, - ], - description: - 'Bounded image bytes from a selected subscription. Complete is a producer\nreturn value, never an emitted item or an executor-cleanup acknowledgement.', - title: 'AgentToolImageFrame', - $schema: 'https://json-schema.org/draft/2020-12/schema', - $defs: { - ImageDetail: { - type: 'string', - enum: ['auto', 'low', 'high', 'original'], + required: ['reason', 'permissions', 'message'], + description: + 'A Host decision made before the requested effect starts. Never inferred from stderr.', }, - }, - }, - resources: {}, - }, - readText: { - mode: 'query', - input: { - type: 'object', - additionalProperties: false, - properties: { - path: { + PermissionDenialReason: { type: 'string', - minLength: 1, + enum: [ + 'requests_disabled', + 'user_denied', + 'protected_boundary', + 'approval_insufficient', + 'grant_expired', + ], }, - offset: { + Permissions: { + type: 'object', + additionalProperties: false, + properties: { + filesystem: { + type: 'array', + items: { + $ref: '#/$defs/Rule', + }, + }, + network: { + $ref: '#/$defs/Network', + }, + }, + required: ['filesystem', 'network'], + }, + Rule: { + type: 'object', + additionalProperties: false, + properties: { + path: { + type: 'string', + }, + scope: { + $ref: '#/$defs/Scope', + }, + access: { + $ref: '#/$defs/Access', + }, + }, + required: ['path', 'scope', 'access'], + description: + 'Paths have executor-native meaning. Validate and materialize them on the\nexecution host, never on a remote Desktop. This document alone grants nothing.', + }, + Scope: { + type: 'string', + enum: ['subtree', 'exact'], + }, + Access: { + type: 'string', + enum: ['read', 'write', 'deny'], + description: + 'Equal-path conflict priority is different from authority breadth: deny wins.', + }, + Network: { + oneOf: [ + { + type: 'string', + enum: ['denied', 'allowed'], + }, + { + type: 'object', + properties: { + restricted: { + type: 'object', + properties: { + destinations: { + type: 'array', + uniqueItems: true, + items: { + $ref: '#/$defs/Destination', + }, + }, + }, + additionalProperties: false, + required: ['destinations'], + }, + }, + required: ['restricted'], + additionalProperties: false, + }, + ], + }, + Destination: { + type: 'object', + additionalProperties: false, + properties: { + host: { + type: 'string', + minLength: 1, + maxLength: 253, + }, + port: { + type: 'integer', + format: 'uint16', + minimum: 1, + maximum: 65535, + }, + }, + required: ['host', 'port'], + description: + 'Destination authority, independent of the upstream proxy used to reach it.\nA domain grant does not authorize its subdomains or other ports.', + }, + PermissionCommand: { + type: 'object', + additionalProperties: false, + properties: { + command: { + type: 'string', + }, + cwd: { + type: 'string', + }, + }, + required: ['command', 'cwd'], + }, + }, + }, + resources: {}, + }, + grep: { + mode: 'query', + input: { + type: 'object', + additionalProperties: false, + properties: { + pattern: { + type: 'string', + }, + path: { + type: 'string', + default: '.', + }, + glob: { + type: 'string', + }, + }, + required: ['pattern'], + title: 'Input', + $schema: 'https://json-schema.org/draft/2020-12/schema', + }, + output: { + oneOf: [ + { + type: 'object', + additionalProperties: false, + properties: { + kind: { + type: 'string', + const: 'permission_denied', + }, + executionState: { + $ref: '#/$defs/NotStarted', + }, + denial: { + $ref: '#/$defs/PermissionDenial', + }, + }, + required: ['kind', 'executionState', 'denial'], + }, + { + type: 'object', + additionalProperties: false, + properties: { + kind: { + type: 'string', + const: 'completed', + }, + value: true, + }, + required: ['kind', 'value'], + }, + { + type: 'object', + additionalProperties: false, + properties: { + kind: { + type: 'string', + const: 'failed', + }, + message: { + type: 'string', + }, + }, + required: ['kind', 'message'], + description: + 'A known completed failure. Uncertain external effects use the original receipt.', + }, + ], + title: 'WorkspaceFileResult', + $schema: 'https://json-schema.org/draft/2020-12/schema', + $defs: { + NotStarted: { + type: 'string', + enum: ['not_started'], + }, + PermissionDenial: { + type: 'object', + additionalProperties: false, + properties: { + reason: { + $ref: '#/$defs/PermissionDenialReason', + }, + permissions: { + $ref: '#/$defs/Permissions', + }, + command: { + anyOf: [ + { + $ref: '#/$defs/PermissionCommand', + }, + { + type: 'null', + }, + ], + }, + message: { + type: 'string', + }, + }, + required: ['reason', 'permissions', 'message'], + description: + 'A Host decision made before the requested effect starts. Never inferred from stderr.', + }, + PermissionDenialReason: { + type: 'string', + enum: [ + 'requests_disabled', + 'user_denied', + 'protected_boundary', + 'approval_insufficient', + 'grant_expired', + ], + }, + Permissions: { + type: 'object', + additionalProperties: false, + properties: { + filesystem: { + type: 'array', + items: { + $ref: '#/$defs/Rule', + }, + }, + network: { + $ref: '#/$defs/Network', + }, + }, + required: ['filesystem', 'network'], + }, + Rule: { + type: 'object', + additionalProperties: false, + properties: { + path: { + type: 'string', + }, + scope: { + $ref: '#/$defs/Scope', + }, + access: { + $ref: '#/$defs/Access', + }, + }, + required: ['path', 'scope', 'access'], + description: + 'Paths have executor-native meaning. Validate and materialize them on the\nexecution host, never on a remote Desktop. This document alone grants nothing.', + }, + Scope: { + type: 'string', + enum: ['subtree', 'exact'], + }, + Access: { + type: 'string', + enum: ['read', 'write', 'deny'], + description: + 'Equal-path conflict priority is different from authority breadth: deny wins.', + }, + Network: { + oneOf: [ + { + type: 'string', + enum: ['denied', 'allowed'], + }, + { + type: 'object', + properties: { + restricted: { + type: 'object', + properties: { + destinations: { + type: 'array', + uniqueItems: true, + items: { + $ref: '#/$defs/Destination', + }, + }, + }, + additionalProperties: false, + required: ['destinations'], + }, + }, + required: ['restricted'], + additionalProperties: false, + }, + ], + }, + Destination: { + type: 'object', + additionalProperties: false, + properties: { + host: { + type: 'string', + minLength: 1, + maxLength: 253, + }, + port: { + type: 'integer', + format: 'uint16', + minimum: 1, + maximum: 65535, + }, + }, + required: ['host', 'port'], + description: + 'Destination authority, independent of the upstream proxy used to reach it.\nA domain grant does not authorize its subdomains or other ports.', + }, + PermissionCommand: { + type: 'object', + additionalProperties: false, + properties: { + command: { + type: 'string', + }, + cwd: { + type: 'string', + }, + }, + required: ['command', 'cwd'], + }, + }, + }, + resources: {}, + }, + publishChange: { + mode: 'mutation', + input: { + type: 'object', + additionalProperties: false, + properties: { + publication: { + $ref: '#/$defs/ExternalPublicationId', + }, + index: { + type: 'integer', + format: 'uint64', + minimum: 0, + }, + }, + required: ['publication', 'index'], + title: 'PublishChange', + $schema: 'https://json-schema.org/draft/2020-12/schema', + $defs: { + ExternalPublicationId: { + type: 'string', + format: 'uuid', + }, + }, + }, + output: { + oneOf: [ + { + type: 'object', + properties: { + kind: { + type: 'string', + const: 'applied', + }, + }, + required: ['kind'], + additionalProperties: false, + }, + { + type: 'object', + additionalProperties: false, + properties: { + kind: { + type: 'string', + const: 'conflict', + }, + reason: { + $ref: '#/$defs/PublicationConflict', + }, + }, + required: ['kind', 'reason'], + }, + { + type: 'object', + additionalProperties: false, + properties: { + kind: { + type: 'string', + const: 'unsupported', + }, + reason: { + $ref: '#/$defs/PublicationUnsupported', + }, + }, + required: ['kind', 'reason'], + }, + { + type: 'object', + additionalProperties: false, + properties: { + kind: { + type: 'string', + const: 'not_attempted', + }, + reason: { + $ref: '#/$defs/PublicationNotAttempted', + }, + }, + required: ['kind', 'reason'], + }, + ], + title: 'PublicationResult', + $schema: 'https://json-schema.org/draft/2020-12/schema', + $defs: { + PublicationConflict: { + type: 'string', + enum: ['workspace_identity', 'entry_type', 'mode', 'content', 'entry_visibility'], + }, + PublicationUnsupported: { + type: 'string', + enum: [ + 'structural_change', + 'uncaptured_workspace', + 'mode_platform', + 'aliased_target', + ], + }, + PublicationNotAttempted: { + type: 'string', + enum: ['cancelled'], + }, + }, + }, + resources: { + content: { + type: { + name: 'maka.bytes', + version: 1, + }, + optional: true, + }, + }, + }, + readImage: { + mode: 'subscription', + input: { + type: 'object', + additionalProperties: false, + properties: { + path: { + type: 'string', + minLength: 1, + }, + offset: { + type: 'integer', + format: 'uint', + minimum: 0, + maximum: 9007199254740991, + }, + limit: { + type: 'integer', + format: 'uint', + minimum: 1, + maximum: 9007199254740991, + }, + }, + required: ['path'], + title: 'ReadInput', + $schema: 'https://json-schema.org/draft/2020-12/schema', + }, + output: { + oneOf: [ + { + type: 'object', + additionalProperties: false, + properties: { + kind: { + type: 'string', + const: 'chunk', + }, + mimeType: { + type: 'string', + pattern: '^image/(png|jpeg|gif|webp)$', + }, + totalBytes: { + type: 'integer', + format: 'uint', + minimum: 1, + maximum: 9007199254740991, + }, + offset: { + type: 'integer', + format: 'uint', + minimum: 0, + maximum: 9007199254740991, + }, + data: { + type: 'string', + minLength: 1, + maxLength: 43692, + }, + }, + required: ['kind', 'mimeType', 'totalBytes', 'offset', 'data'], + }, + { + type: 'object', + additionalProperties: false, + properties: { + kind: { + type: 'string', + const: 'complete', + }, + detail: { + anyOf: [ + { + $ref: '#/$defs/ImageDetail', + }, + { + type: 'null', + }, + ], + }, + }, + required: ['kind'], + }, + ], + description: + 'Bounded image bytes from a selected subscription. Complete is a producer\nreturn value, never an emitted item or an executor-cleanup acknowledgement.', + title: 'AgentToolImageFrame', + $schema: 'https://json-schema.org/draft/2020-12/schema', + $defs: { + ImageDetail: { + type: 'string', + enum: ['auto', 'low', 'high', 'original'], + }, + }, + }, + resources: {}, + }, + readText: { + mode: 'query', + input: { + type: 'object', + additionalProperties: false, + properties: { + path: { + type: 'string', + minLength: 1, + }, + offset: { type: 'integer', format: 'uint', minimum: 0, maximum: 9007199254740991, }, - limit: { - type: 'integer', - format: 'uint', - minimum: 1, - maximum: 9007199254740991, + limit: { + type: 'integer', + format: 'uint', + minimum: 1, + maximum: 9007199254740991, + }, + }, + required: ['path'], + title: 'ReadInput', + $schema: 'https://json-schema.org/draft/2020-12/schema', + }, + output: { + oneOf: [ + { + type: 'object', + additionalProperties: false, + properties: { + kind: { + type: 'string', + const: 'permission_denied', + }, + executionState: { + $ref: '#/$defs/NotStarted', + }, + denial: { + $ref: '#/$defs/PermissionDenial', + }, + }, + required: ['kind', 'executionState', 'denial'], + }, + { + type: 'object', + additionalProperties: false, + properties: { + kind: { + type: 'string', + const: 'completed', + }, + value: true, + }, + required: ['kind', 'value'], + }, + { + type: 'object', + additionalProperties: false, + properties: { + kind: { + type: 'string', + const: 'failed', + }, + message: { + type: 'string', + }, + }, + required: ['kind', 'message'], + description: + 'A known completed failure. Uncertain external effects use the original receipt.', + }, + ], + title: 'WorkspaceFileResult', + $schema: 'https://json-schema.org/draft/2020-12/schema', + $defs: { + NotStarted: { + type: 'string', + enum: ['not_started'], + }, + PermissionDenial: { + type: 'object', + additionalProperties: false, + properties: { + reason: { + $ref: '#/$defs/PermissionDenialReason', + }, + permissions: { + $ref: '#/$defs/Permissions', + }, + command: { + anyOf: [ + { + $ref: '#/$defs/PermissionCommand', + }, + { + type: 'null', + }, + ], + }, + message: { + type: 'string', + }, + }, + required: ['reason', 'permissions', 'message'], + description: + 'A Host decision made before the requested effect starts. Never inferred from stderr.', + }, + PermissionDenialReason: { + type: 'string', + enum: [ + 'requests_disabled', + 'user_denied', + 'protected_boundary', + 'approval_insufficient', + 'grant_expired', + ], + }, + Permissions: { + type: 'object', + additionalProperties: false, + properties: { + filesystem: { + type: 'array', + items: { + $ref: '#/$defs/Rule', + }, + }, + network: { + $ref: '#/$defs/Network', + }, + }, + required: ['filesystem', 'network'], + }, + Rule: { + type: 'object', + additionalProperties: false, + properties: { + path: { + type: 'string', + }, + scope: { + $ref: '#/$defs/Scope', + }, + access: { + $ref: '#/$defs/Access', + }, + }, + required: ['path', 'scope', 'access'], + description: + 'Paths have executor-native meaning. Validate and materialize them on the\nexecution host, never on a remote Desktop. This document alone grants nothing.', + }, + Scope: { + type: 'string', + enum: ['subtree', 'exact'], + }, + Access: { + type: 'string', + enum: ['read', 'write', 'deny'], + description: + 'Equal-path conflict priority is different from authority breadth: deny wins.', + }, + Network: { + oneOf: [ + { + type: 'string', + enum: ['denied', 'allowed'], + }, + { + type: 'object', + properties: { + restricted: { + type: 'object', + properties: { + destinations: { + type: 'array', + uniqueItems: true, + items: { + $ref: '#/$defs/Destination', + }, + }, + }, + additionalProperties: false, + required: ['destinations'], + }, + }, + required: ['restricted'], + additionalProperties: false, + }, + ], + }, + Destination: { + type: 'object', + additionalProperties: false, + properties: { + host: { + type: 'string', + minLength: 1, + maxLength: 253, + }, + port: { + type: 'integer', + format: 'uint16', + minimum: 1, + maximum: 65535, + }, + }, + required: ['host', 'port'], + description: + 'Destination authority, independent of the upstream proxy used to reach it.\nA domain grant does not authorize its subdomains or other ports.', + }, + PermissionCommand: { + type: 'object', + additionalProperties: false, + properties: { + command: { + type: 'string', + }, + cwd: { + type: 'string', + }, + }, + required: ['command', 'cwd'], + }, + }, + }, + resources: {}, + }, + write: { + mode: 'mutation', + input: { + type: 'object', + additionalProperties: false, + properties: { + path: { + type: 'string', + minLength: 1, + }, + content: { + type: 'string', + }, + }, + required: ['path', 'content'], + title: 'WriteInput', + $schema: 'https://json-schema.org/draft/2020-12/schema', + }, + output: { + oneOf: [ + { + type: 'object', + additionalProperties: false, + properties: { + kind: { + type: 'string', + const: 'permission_denied', + }, + executionState: { + $ref: '#/$defs/NotStarted', + }, + denial: { + $ref: '#/$defs/PermissionDenial', + }, + }, + required: ['kind', 'executionState', 'denial'], + }, + { + type: 'object', + additionalProperties: false, + properties: { + kind: { + type: 'string', + const: 'completed', + }, + value: true, + }, + required: ['kind', 'value'], + }, + { + type: 'object', + additionalProperties: false, + properties: { + kind: { + type: 'string', + const: 'failed', + }, + message: { + type: 'string', + }, + }, + required: ['kind', 'message'], + description: + 'A known completed failure. Uncertain external effects use the original receipt.', + }, + ], + title: 'WorkspaceFileResult', + $schema: 'https://json-schema.org/draft/2020-12/schema', + $defs: { + NotStarted: { + type: 'string', + enum: ['not_started'], + }, + PermissionDenial: { + type: 'object', + additionalProperties: false, + properties: { + reason: { + $ref: '#/$defs/PermissionDenialReason', + }, + permissions: { + $ref: '#/$defs/Permissions', + }, + command: { + anyOf: [ + { + $ref: '#/$defs/PermissionCommand', + }, + { + type: 'null', + }, + ], + }, + message: { + type: 'string', + }, + }, + required: ['reason', 'permissions', 'message'], + description: + 'A Host decision made before the requested effect starts. Never inferred from stderr.', + }, + PermissionDenialReason: { + type: 'string', + enum: [ + 'requests_disabled', + 'user_denied', + 'protected_boundary', + 'approval_insufficient', + 'grant_expired', + ], + }, + Permissions: { + type: 'object', + additionalProperties: false, + properties: { + filesystem: { + type: 'array', + items: { + $ref: '#/$defs/Rule', + }, + }, + network: { + $ref: '#/$defs/Network', + }, + }, + required: ['filesystem', 'network'], + }, + Rule: { + type: 'object', + additionalProperties: false, + properties: { + path: { + type: 'string', + }, + scope: { + $ref: '#/$defs/Scope', + }, + access: { + $ref: '#/$defs/Access', + }, + }, + required: ['path', 'scope', 'access'], + description: + 'Paths have executor-native meaning. Validate and materialize them on the\nexecution host, never on a remote Desktop. This document alone grants nothing.', + }, + Scope: { + type: 'string', + enum: ['subtree', 'exact'], + }, + Access: { + type: 'string', + enum: ['read', 'write', 'deny'], + description: + 'Equal-path conflict priority is different from authority breadth: deny wins.', + }, + Network: { + oneOf: [ + { + type: 'string', + enum: ['denied', 'allowed'], + }, + { + type: 'object', + properties: { + restricted: { + type: 'object', + properties: { + destinations: { + type: 'array', + uniqueItems: true, + items: { + $ref: '#/$defs/Destination', + }, + }, + }, + additionalProperties: false, + required: ['destinations'], + }, + }, + required: ['restricted'], + additionalProperties: false, + }, + ], + }, + Destination: { + type: 'object', + additionalProperties: false, + properties: { + host: { + type: 'string', + minLength: 1, + maxLength: 253, + }, + port: { + type: 'integer', + format: 'uint16', + minimum: 1, + maximum: 65535, + }, + }, + required: ['host', 'port'], + description: + 'Destination authority, independent of the upstream proxy used to reach it.\nA domain grant does not authorize its subdomains or other ports.', + }, + PermissionCommand: { + type: 'object', + additionalProperties: false, + properties: { + command: { + type: 'string', + }, + cwd: { + type: 'string', + }, + }, + required: ['command', 'cwd'], + }, + }, + }, + resources: {}, + }, + }, + }, + }, + workspaceProcesses: { + name: 'maka.workspace.Processes', + version: 1, + transport: 'remote', + interface: { + kind: 'service', + methods: { + execute: { + mode: 'mutation', + input: { + type: 'object', + additionalProperties: false, + properties: { + command: { + type: 'string', + minLength: 1, + }, + shell: { + anyOf: [ + { + $ref: '#/$defs/ShellPolicy', + }, + { + type: 'null', + }, + ], + description: + 'Explicit interpreter choice; absent uses the Runtime Host preference.', + }, + login: { + type: 'boolean', + description: "Load the shell's login profile. Set false to skip it.", + default: true, + }, + timeout_ms: { + type: ['integer', 'null'], + format: 'uint64', + minimum: 1, + }, + run_in_background: { + type: 'boolean', + default: false, + }, + pty: { + type: 'boolean', + description: 'Allocate a terminal; requires run_in_background=true.', + default: false, + }, + additional_permissions: { + anyOf: [ + { + $ref: '#/$defs/Permissions', + }, + { + type: 'null', + }, + ], + description: + 'Request additional filesystem or network access before this command.\nOn Linux/Windows, request an existing parent directory when creating a new path.', + }, + justification: { + type: ['string', 'null'], + description: + 'Explain why the additional access is necessary. Required with additional_permissions.', + }, + }, + required: ['command'], + title: 'Execute', + $schema: 'https://json-schema.org/draft/2020-12/schema', + $defs: { + ShellPolicy: { + type: 'object', + additionalProperties: false, + properties: { + preference: { + $ref: '#/$defs/ShellPreference', + }, + executable: { + type: 'string', + }, + titles: { + type: 'boolean', + }, + }, + required: ['preference', 'executable', 'titles'], + }, + ShellPreference: { + type: 'string', + enum: ['auto', 'git_bash'], + }, + Permissions: { + type: 'object', + additionalProperties: false, + properties: { + filesystem: { + type: 'array', + items: { + $ref: '#/$defs/Rule', + }, + }, + network: { + $ref: '#/$defs/Network', + }, + }, + required: ['filesystem', 'network'], + }, + Rule: { + type: 'object', + additionalProperties: false, + properties: { + path: { + type: 'string', + }, + scope: { + $ref: '#/$defs/Scope', + }, + access: { + $ref: '#/$defs/Access', + }, + }, + required: ['path', 'scope', 'access'], + description: + 'Paths have executor-native meaning. Validate and materialize them on the\nexecution host, never on a remote Desktop. This document alone grants nothing.', + }, + Scope: { + type: 'string', + enum: ['subtree', 'exact'], + }, + Access: { + type: 'string', + enum: ['read', 'write', 'deny'], + description: + 'Equal-path conflict priority is different from authority breadth: deny wins.', + }, + Network: { + oneOf: [ + { + type: 'string', + enum: ['denied', 'allowed'], + }, + { + type: 'object', + properties: { + restricted: { + type: 'object', + properties: { + destinations: { + type: 'array', + uniqueItems: true, + items: { + $ref: '#/$defs/Destination', + }, + }, + }, + additionalProperties: false, + required: ['destinations'], + }, + }, + required: ['restricted'], + additionalProperties: false, + }, + ], + }, + Destination: { + type: 'object', + additionalProperties: false, + properties: { + host: { + type: 'string', + minLength: 1, + maxLength: 253, + }, + port: { + type: 'integer', + format: 'uint16', + minimum: 1, + maximum: 65535, + }, + }, + required: ['host', 'port'], + description: + 'Destination authority, independent of the upstream proxy used to reach it.\nA domain grant does not authorize its subdomains or other ports.', + }, + }, + }, + output: { + oneOf: [ + { + type: 'object', + additionalProperties: false, + properties: { + kind: { + type: 'string', + const: 'permission_denied', + }, + executionState: { + $ref: '#/$defs/NotStarted', + }, + denial: { + $ref: '#/$defs/PermissionDenial', + }, + }, + required: ['kind', 'executionState', 'denial'], + }, + { + type: 'object', + additionalProperties: false, + properties: { + kind: { + type: 'string', + const: 'completed', + }, + value: true, + }, + required: ['kind', 'value'], + }, + { + type: 'object', + additionalProperties: false, + properties: { + kind: { + type: 'string', + const: 'failed', + }, + message: { + type: 'string', + }, + }, + required: ['kind', 'message'], + }, + ], + title: 'WorkspaceProcessResult', + $schema: 'https://json-schema.org/draft/2020-12/schema', + $defs: { + NotStarted: { + type: 'string', + enum: ['not_started'], + }, + PermissionDenial: { + type: 'object', + additionalProperties: false, + properties: { + reason: { + $ref: '#/$defs/PermissionDenialReason', + }, + permissions: { + $ref: '#/$defs/Permissions', + }, + command: { + anyOf: [ + { + $ref: '#/$defs/PermissionCommand', + }, + { + type: 'null', + }, + ], + }, + message: { + type: 'string', + }, + }, + required: ['reason', 'permissions', 'message'], + description: + 'A Host decision made before the requested effect starts. Never inferred from stderr.', + }, + PermissionDenialReason: { + type: 'string', + enum: [ + 'requests_disabled', + 'user_denied', + 'protected_boundary', + 'approval_insufficient', + 'grant_expired', + ], + }, + Permissions: { + type: 'object', + additionalProperties: false, + properties: { + filesystem: { + type: 'array', + items: { + $ref: '#/$defs/Rule', + }, + }, + network: { + $ref: '#/$defs/Network', + }, + }, + required: ['filesystem', 'network'], + }, + Rule: { + type: 'object', + additionalProperties: false, + properties: { + path: { + type: 'string', + }, + scope: { + $ref: '#/$defs/Scope', + }, + access: { + $ref: '#/$defs/Access', + }, + }, + required: ['path', 'scope', 'access'], + description: + 'Paths have executor-native meaning. Validate and materialize them on the\nexecution host, never on a remote Desktop. This document alone grants nothing.', + }, + Scope: { + type: 'string', + enum: ['subtree', 'exact'], + }, + Access: { + type: 'string', + enum: ['read', 'write', 'deny'], + description: + 'Equal-path conflict priority is different from authority breadth: deny wins.', + }, + Network: { + oneOf: [ + { + type: 'string', + enum: ['denied', 'allowed'], + }, + { + type: 'object', + properties: { + restricted: { + type: 'object', + properties: { + destinations: { + type: 'array', + uniqueItems: true, + items: { + $ref: '#/$defs/Destination', + }, + }, + }, + additionalProperties: false, + required: ['destinations'], + }, + }, + required: ['restricted'], + additionalProperties: false, + }, + ], + }, + Destination: { + type: 'object', + additionalProperties: false, + properties: { + host: { + type: 'string', + minLength: 1, + maxLength: 253, + }, + port: { + type: 'integer', + format: 'uint16', + minimum: 1, + maximum: 65535, + }, + }, + required: ['host', 'port'], + description: + 'Destination authority, independent of the upstream proxy used to reach it.\nA domain grant does not authorize its subdomains or other ports.', + }, + PermissionCommand: { + type: 'object', + additionalProperties: false, + properties: { + command: { + type: 'string', + }, + cwd: { + type: 'string', + }, + }, + required: ['command', 'cwd'], }, }, - required: ['path'], - title: 'ReadInput', + }, + resources: {}, + }, + health: { + mode: 'mutation', + input: { + type: 'object', + additionalProperties: false, + properties: { + ref: { + type: 'string', + }, + include_logs: { + type: 'boolean', + default: false, + }, + url: { + type: ['string', 'null'], + }, + }, + required: ['ref'], + title: 'WorkspaceProcessHealth', $schema: 'https://json-schema.org/draft/2020-12/schema', }, output: { oneOf: [ + { + type: 'object', + additionalProperties: false, + properties: { + kind: { + type: 'string', + const: 'permission_denied', + }, + executionState: { + $ref: '#/$defs/NotStarted', + }, + denial: { + $ref: '#/$defs/PermissionDenial', + }, + }, + required: ['kind', 'executionState', 'denial'], + }, { type: 'object', additionalProperties: false, @@ -42743,48 +44262,209 @@ export const contracts: { }, required: ['kind', 'value'], }, - { + { + type: 'object', + additionalProperties: false, + properties: { + kind: { + type: 'string', + const: 'failed', + }, + message: { + type: 'string', + }, + }, + required: ['kind', 'message'], + }, + ], + title: 'WorkspaceProcessResult', + $schema: 'https://json-schema.org/draft/2020-12/schema', + $defs: { + NotStarted: { + type: 'string', + enum: ['not_started'], + }, + PermissionDenial: { + type: 'object', + additionalProperties: false, + properties: { + reason: { + $ref: '#/$defs/PermissionDenialReason', + }, + permissions: { + $ref: '#/$defs/Permissions', + }, + command: { + anyOf: [ + { + $ref: '#/$defs/PermissionCommand', + }, + { + type: 'null', + }, + ], + }, + message: { + type: 'string', + }, + }, + required: ['reason', 'permissions', 'message'], + description: + 'A Host decision made before the requested effect starts. Never inferred from stderr.', + }, + PermissionDenialReason: { + type: 'string', + enum: [ + 'requests_disabled', + 'user_denied', + 'protected_boundary', + 'approval_insufficient', + 'grant_expired', + ], + }, + Permissions: { + type: 'object', + additionalProperties: false, + properties: { + filesystem: { + type: 'array', + items: { + $ref: '#/$defs/Rule', + }, + }, + network: { + $ref: '#/$defs/Network', + }, + }, + required: ['filesystem', 'network'], + }, + Rule: { + type: 'object', + additionalProperties: false, + properties: { + path: { + type: 'string', + }, + scope: { + $ref: '#/$defs/Scope', + }, + access: { + $ref: '#/$defs/Access', + }, + }, + required: ['path', 'scope', 'access'], + description: + 'Paths have executor-native meaning. Validate and materialize them on the\nexecution host, never on a remote Desktop. This document alone grants nothing.', + }, + Scope: { + type: 'string', + enum: ['subtree', 'exact'], + }, + Access: { + type: 'string', + enum: ['read', 'write', 'deny'], + description: + 'Equal-path conflict priority is different from authority breadth: deny wins.', + }, + Network: { + oneOf: [ + { + type: 'string', + enum: ['denied', 'allowed'], + }, + { + type: 'object', + properties: { + restricted: { + type: 'object', + properties: { + destinations: { + type: 'array', + uniqueItems: true, + items: { + $ref: '#/$defs/Destination', + }, + }, + }, + additionalProperties: false, + required: ['destinations'], + }, + }, + required: ['restricted'], + additionalProperties: false, + }, + ], + }, + Destination: { + type: 'object', + additionalProperties: false, + properties: { + host: { + type: 'string', + minLength: 1, + maxLength: 253, + }, + port: { + type: 'integer', + format: 'uint16', + minimum: 1, + maximum: 65535, + }, + }, + required: ['host', 'port'], + description: + 'Destination authority, independent of the upstream proxy used to reach it.\nA domain grant does not authorize its subdomains or other ports.', + }, + PermissionCommand: { type: 'object', additionalProperties: false, properties: { - kind: { + command: { type: 'string', - const: 'failed', }, - message: { + cwd: { type: 'string', }, }, - required: ['kind', 'message'], - description: - 'A known completed failure. Uncertain external effects use the original receipt.', + required: ['command', 'cwd'], }, - ], - title: 'WorkspaceFileResult', - $schema: 'https://json-schema.org/draft/2020-12/schema', + }, }, resources: {}, }, - write: { - mode: 'mutation', + read: { + mode: 'query', input: { type: 'object', additionalProperties: false, properties: { - path: { - type: 'string', - minLength: 1, - }, - content: { + ref: { type: 'string', }, }, - required: ['path', 'content'], - title: 'WriteInput', + required: ['ref'], + title: 'Reference', $schema: 'https://json-schema.org/draft/2020-12/schema', }, output: { oneOf: [ + { + type: 'object', + additionalProperties: false, + properties: { + kind: { + type: 'string', + const: 'permission_denied', + }, + executionState: { + $ref: '#/$defs/NotStarted', + }, + denial: { + $ref: '#/$defs/PermissionDenial', + }, + }, + required: ['kind', 'executionState', 'denial'], + }, { type: 'object', additionalProperties: false, @@ -42810,107 +44490,52 @@ export const contracts: { }, }, required: ['kind', 'message'], - description: - 'A known completed failure. Uncertain external effects use the original receipt.', }, ], - title: 'WorkspaceFileResult', + title: 'WorkspaceProcessResult', $schema: 'https://json-schema.org/draft/2020-12/schema', - }, - resources: {}, - }, - }, - }, - }, - workspaceProcesses: { - name: 'maka.workspace.Processes', - version: 1, - transport: 'remote', - interface: { - kind: 'service', - methods: { - execute: { - mode: 'mutation', - input: { - type: 'object', - additionalProperties: false, - properties: { - command: { + $defs: { + NotStarted: { type: 'string', - minLength: 1, - }, - shell: { - anyOf: [ - { - $ref: '#/$defs/ShellPolicy', - }, - { - type: 'null', - }, - ], - description: - 'Explicit interpreter choice; absent uses the Runtime Host preference.', - }, - login: { - type: 'boolean', - description: "Load the shell's login profile. Set false to skip it.", - default: true, - }, - timeout_ms: { - type: ['integer', 'null'], - format: 'uint64', - minimum: 1, - }, - run_in_background: { - type: 'boolean', - default: false, - }, - pty: { - type: 'boolean', - description: 'Allocate a terminal; requires run_in_background=true.', - default: false, - }, - additional_permissions: { - anyOf: [ - { - $ref: '#/$defs/Permissions', - }, - { - type: 'null', - }, - ], - description: - 'Request additional filesystem or network access before this command.\nOn Linux/Windows, request an existing parent directory when creating a new path.', - }, - justification: { - type: ['string', 'null'], - description: - 'Explain why the additional access is necessary. Required with additional_permissions.', + enum: ['not_started'], }, - }, - required: ['command'], - title: 'Execute', - $schema: 'https://json-schema.org/draft/2020-12/schema', - $defs: { - ShellPolicy: { + PermissionDenial: { type: 'object', additionalProperties: false, properties: { - preference: { - $ref: '#/$defs/ShellPreference', + reason: { + $ref: '#/$defs/PermissionDenialReason', }, - executable: { - type: 'string', + permissions: { + $ref: '#/$defs/Permissions', }, - titles: { - type: 'boolean', + command: { + anyOf: [ + { + $ref: '#/$defs/PermissionCommand', + }, + { + type: 'null', + }, + ], + }, + message: { + type: 'string', }, }, - required: ['preference', 'executable', 'titles'], + required: ['reason', 'permissions', 'message'], + description: + 'A Host decision made before the requested effect starts. Never inferred from stderr.', }, - ShellPreference: { + PermissionDenialReason: { type: 'string', - enum: ['auto', 'git_bash'], + enum: [ + 'requests_disabled', + 'user_denied', + 'protected_boundary', + 'approval_insufficient', + 'grant_expired', + ], }, Permissions: { type: 'object', @@ -43005,43 +44630,24 @@ export const contracts: { description: 'Destination authority, independent of the upstream proxy used to reach it.\nA domain grant does not authorize its subdomains or other ports.', }, - }, - }, - output: { - oneOf: [ - { - type: 'object', - additionalProperties: false, - properties: { - kind: { - type: 'string', - const: 'completed', - }, - value: true, - }, - required: ['kind', 'value'], - }, - { + PermissionCommand: { type: 'object', additionalProperties: false, properties: { - kind: { + command: { type: 'string', - const: 'failed', }, - message: { + cwd: { type: 'string', }, }, - required: ['kind', 'message'], + required: ['command', 'cwd'], }, - ], - title: 'WorkspaceProcessResult', - $schema: 'https://json-schema.org/draft/2020-12/schema', + }, }, resources: {}, }, - health: { + stop: { mode: 'mutation', input: { type: 'object', @@ -43050,20 +44656,30 @@ export const contracts: { ref: { type: 'string', }, - include_logs: { - type: 'boolean', - default: false, - }, - url: { - type: ['string', 'null'], - }, }, required: ['ref'], - title: 'WorkspaceProcessHealth', + title: 'Reference', $schema: 'https://json-schema.org/draft/2020-12/schema', }, output: { oneOf: [ + { + type: 'object', + additionalProperties: false, + properties: { + kind: { + type: 'string', + const: 'permission_denied', + }, + executionState: { + $ref: '#/$defs/NotStarted', + }, + denial: { + $ref: '#/$defs/PermissionDenial', + }, + }, + required: ['kind', 'executionState', 'denial'], + }, { type: 'object', additionalProperties: false, @@ -43093,102 +44709,156 @@ export const contracts: { ], title: 'WorkspaceProcessResult', $schema: 'https://json-schema.org/draft/2020-12/schema', - }, - resources: {}, - }, - read: { - mode: 'query', - input: { - type: 'object', - additionalProperties: false, - properties: { - ref: { + $defs: { + NotStarted: { type: 'string', + enum: ['not_started'], }, - }, - required: ['ref'], - title: 'Reference', - $schema: 'https://json-schema.org/draft/2020-12/schema', - }, - output: { - oneOf: [ - { + PermissionDenial: { type: 'object', additionalProperties: false, properties: { - kind: { + reason: { + $ref: '#/$defs/PermissionDenialReason', + }, + permissions: { + $ref: '#/$defs/Permissions', + }, + command: { + anyOf: [ + { + $ref: '#/$defs/PermissionCommand', + }, + { + type: 'null', + }, + ], + }, + message: { type: 'string', - const: 'completed', }, - value: true, }, - required: ['kind', 'value'], + required: ['reason', 'permissions', 'message'], + description: + 'A Host decision made before the requested effect starts. Never inferred from stderr.', + }, + PermissionDenialReason: { + type: 'string', + enum: [ + 'requests_disabled', + 'user_denied', + 'protected_boundary', + 'approval_insufficient', + 'grant_expired', + ], + }, + Permissions: { + type: 'object', + additionalProperties: false, + properties: { + filesystem: { + type: 'array', + items: { + $ref: '#/$defs/Rule', + }, + }, + network: { + $ref: '#/$defs/Network', + }, + }, + required: ['filesystem', 'network'], }, - { + Rule: { type: 'object', additionalProperties: false, properties: { - kind: { + path: { type: 'string', - const: 'failed', }, - message: { - type: 'string', + scope: { + $ref: '#/$defs/Scope', + }, + access: { + $ref: '#/$defs/Access', }, }, - required: ['kind', 'message'], + required: ['path', 'scope', 'access'], + description: + 'Paths have executor-native meaning. Validate and materialize them on the\nexecution host, never on a remote Desktop. This document alone grants nothing.', }, - ], - title: 'WorkspaceProcessResult', - $schema: 'https://json-schema.org/draft/2020-12/schema', - }, - resources: {}, - }, - stop: { - mode: 'mutation', - input: { - type: 'object', - additionalProperties: false, - properties: { - ref: { + Scope: { type: 'string', + enum: ['subtree', 'exact'], }, - }, - required: ['ref'], - title: 'Reference', - $schema: 'https://json-schema.org/draft/2020-12/schema', - }, - output: { - oneOf: [ - { + Access: { + type: 'string', + enum: ['read', 'write', 'deny'], + description: + 'Equal-path conflict priority is different from authority breadth: deny wins.', + }, + Network: { + oneOf: [ + { + type: 'string', + enum: ['denied', 'allowed'], + }, + { + type: 'object', + properties: { + restricted: { + type: 'object', + properties: { + destinations: { + type: 'array', + uniqueItems: true, + items: { + $ref: '#/$defs/Destination', + }, + }, + }, + additionalProperties: false, + required: ['destinations'], + }, + }, + required: ['restricted'], + additionalProperties: false, + }, + ], + }, + Destination: { type: 'object', additionalProperties: false, properties: { - kind: { + host: { type: 'string', - const: 'completed', + minLength: 1, + maxLength: 253, + }, + port: { + type: 'integer', + format: 'uint16', + minimum: 1, + maximum: 65535, }, - value: true, }, - required: ['kind', 'value'], + required: ['host', 'port'], + description: + 'Destination authority, independent of the upstream proxy used to reach it.\nA domain grant does not authorize its subdomains or other ports.', }, - { + PermissionCommand: { type: 'object', additionalProperties: false, properties: { - kind: { + command: { type: 'string', - const: 'failed', }, - message: { + cwd: { type: 'string', }, }, - required: ['kind', 'message'], + required: ['command', 'cwd'], }, - ], - title: 'WorkspaceProcessResult', - $schema: 'https://json-schema.org/draft/2020-12/schema', + }, }, resources: {}, }, @@ -43262,6 +44932,23 @@ export const contracts: { }, output: { oneOf: [ + { + type: 'object', + additionalProperties: false, + properties: { + kind: { + type: 'string', + const: 'permission_denied', + }, + executionState: { + $ref: '#/$defs/NotStarted', + }, + denial: { + $ref: '#/$defs/PermissionDenial', + }, + }, + required: ['kind', 'executionState', 'denial'], + }, { type: 'object', additionalProperties: false, @@ -43291,6 +44978,156 @@ export const contracts: { ], title: 'WorkspaceProcessResult', $schema: 'https://json-schema.org/draft/2020-12/schema', + $defs: { + NotStarted: { + type: 'string', + enum: ['not_started'], + }, + PermissionDenial: { + type: 'object', + additionalProperties: false, + properties: { + reason: { + $ref: '#/$defs/PermissionDenialReason', + }, + permissions: { + $ref: '#/$defs/Permissions', + }, + command: { + anyOf: [ + { + $ref: '#/$defs/PermissionCommand', + }, + { + type: 'null', + }, + ], + }, + message: { + type: 'string', + }, + }, + required: ['reason', 'permissions', 'message'], + description: + 'A Host decision made before the requested effect starts. Never inferred from stderr.', + }, + PermissionDenialReason: { + type: 'string', + enum: [ + 'requests_disabled', + 'user_denied', + 'protected_boundary', + 'approval_insufficient', + 'grant_expired', + ], + }, + Permissions: { + type: 'object', + additionalProperties: false, + properties: { + filesystem: { + type: 'array', + items: { + $ref: '#/$defs/Rule', + }, + }, + network: { + $ref: '#/$defs/Network', + }, + }, + required: ['filesystem', 'network'], + }, + Rule: { + type: 'object', + additionalProperties: false, + properties: { + path: { + type: 'string', + }, + scope: { + $ref: '#/$defs/Scope', + }, + access: { + $ref: '#/$defs/Access', + }, + }, + required: ['path', 'scope', 'access'], + description: + 'Paths have executor-native meaning. Validate and materialize them on the\nexecution host, never on a remote Desktop. This document alone grants nothing.', + }, + Scope: { + type: 'string', + enum: ['subtree', 'exact'], + }, + Access: { + type: 'string', + enum: ['read', 'write', 'deny'], + description: + 'Equal-path conflict priority is different from authority breadth: deny wins.', + }, + Network: { + oneOf: [ + { + type: 'string', + enum: ['denied', 'allowed'], + }, + { + type: 'object', + properties: { + restricted: { + type: 'object', + properties: { + destinations: { + type: 'array', + uniqueItems: true, + items: { + $ref: '#/$defs/Destination', + }, + }, + }, + additionalProperties: false, + required: ['destinations'], + }, + }, + required: ['restricted'], + additionalProperties: false, + }, + ], + }, + Destination: { + type: 'object', + additionalProperties: false, + properties: { + host: { + type: 'string', + minLength: 1, + maxLength: 253, + }, + port: { + type: 'integer', + format: 'uint16', + minimum: 1, + maximum: 65535, + }, + }, + required: ['host', 'port'], + description: + 'Destination authority, independent of the upstream proxy used to reach it.\nA domain grant does not authorize its subdomains or other ports.', + }, + PermissionCommand: { + type: 'object', + additionalProperties: false, + properties: { + command: { + type: 'string', + }, + cwd: { + type: 'string', + }, + }, + required: ['command', 'cwd'], + }, + }, }, resources: {}, }, diff --git a/platform/computer-use/src/desktop/worker.rs b/platform/computer-use/src/desktop/worker.rs index 6568e75f4e0..70d291188a7 100644 --- a/platform/computer-use/src/desktop/worker.rs +++ b/platform/computer-use/src/desktop/worker.rs @@ -167,6 +167,7 @@ enum Failure { impl From for Failure { fn from(error: ToolError) -> Self { match error { + ToolError::PermissionDenied(denial) => Self::Failed(denial.message), ToolError::Failed(message) => Self::Failed(message), ToolError::Native(error) => Self::Native(error), ToolError::OutcomeUnknown(message) | ToolError::OperationUnknown { message, .. } => { @@ -572,7 +573,13 @@ impl Process { fn authorization_cleanup(result: Result<(), ToolError>) -> Option { match result { - Ok(()) | Err(ToolError::Failed(_) | ToolError::Native(_) | ToolError::Io { .. }) => None, + Ok(()) + | Err( + ToolError::Failed(_) + | ToolError::PermissionDenied(_) + | ToolError::Native(_) + | ToolError::Io { .. }, + ) => None, Err(error) => { let message = format!("Browser authorization cleanup was not confirmed: {error}"); Some(match error { diff --git a/plugins/agent/authority/src/permissions.rs b/plugins/agent/authority/src/permissions.rs index 66efa313fac..4a174b03bc4 100644 --- a/plugins/agent/authority/src/permissions.rs +++ b/plugins/agent/authority/src/permissions.rs @@ -25,7 +25,7 @@ use maka_agent_facts::interaction::InteractionRecord; use maka_agent_facts::interaction::InteractionRequest; use maka_agent_facts::interaction::PermissionDecision; use maka_agent_facts::interaction::PermissionRequest; -use maka_agent_facts::tools::ToolError; +use maka_agent_facts::tools::{PermissionDenial, PermissionDenialReason, ToolError}; use maka_event_log::{StoreError, sessions::SessionRecord}; use sha2::{Digest, Sha256}; use tokio_util::sync::CancellationToken; @@ -66,7 +66,10 @@ pub async fn request( ) -> Result { let configuration = access.live_configuration().await?; if !configuration.permissions.request_permissions { - return Err(failed( + return Err(denied( + PermissionDenialReason::RequestsDisabled, + &request.permissions, + request.command.as_ref(), "Workspace policy does not allow requesting additional permissions", )); } @@ -103,7 +106,7 @@ pub async fn request( )), }, basis: configuration.permission_basis(), - request, + request: request.clone(), }, outcome: None, }; @@ -126,14 +129,34 @@ pub async fn request( if cancellation.is_cancelled() { return Err(failed("Permission request cancelled")); } - if !matches!( - outcome, + match outcome { InteractionOutcome::PermissionsDecision { decision: PermissionDecision::Allow { .. }, .. + } => {} + InteractionOutcome::PermissionsDecision { + decision: PermissionDecision::Deny, + .. + } => { + return Err(denied( + PermissionDenialReason::UserDenied, + &request.permissions, + request.command.as_ref(), + "User denied additional permissions. Do not repeat this request; continue within existing permissions or report the blocked operation.", + )); + } + _ => { + let current = access.live_configuration().await?; + if current.permission_basis() != configuration.permission_basis() { + return Err(denied( + PermissionDenialReason::GrantExpired, + &request.permissions, + request.command.as_ref(), + "Permission authority changed while awaiting approval", + )); + } + return Err(failed("Permission request closed without a decision")); } - ) { - return Err(failed("Additional permissions were not granted")); } access .permission_grants(target, &configuration.permission_basis()) @@ -141,7 +164,28 @@ pub async fn request( .map_err(|_| failed("Permission observation unavailable"))? .into_iter() .find(|grant| grant.request_id == record.request_id) - .ok_or_else(|| failed("Approved permissions are no longer current")) + .ok_or_else(|| { + denied( + PermissionDenialReason::GrantExpired, + &request.permissions, + request.command.as_ref(), + "Approved permissions are no longer current", + ) + }) +} + +pub fn denied( + reason: PermissionDenialReason, + permissions: &maka_sandbox::grant::Permissions, + command: Option<&maka_agent_facts::interaction::PermissionCommand>, + message: impl Into, +) -> ToolError { + ToolError::PermissionDenied(Box::new(PermissionDenial { + reason, + permissions: permissions.clone(), + command: command.cloned(), + message: message.into(), + })) } fn failed(message: impl Into) -> ToolError { diff --git a/plugins/agent/facts/src/tools.rs b/plugins/agent/facts/src/tools.rs index caa9dd16274..a4e6cdd950d 100644 --- a/plugins/agent/facts/src/tools.rs +++ b/plugins/agent/facts/src/tools.rs @@ -91,8 +91,34 @@ pub struct NativeToolError { pub message: String, } +/// A Host decision made before the requested effect starts. Never inferred from stderr. +#[derive( + Clone, Debug, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema, thiserror::Error, +)] +#[serde(rename_all = "camelCase", deny_unknown_fields)] +#[error("{message}")] +pub struct PermissionDenial { + pub reason: PermissionDenialReason, + pub permissions: maka_sandbox::grant::Permissions, + #[serde(skip_serializing_if = "Option::is_none")] + pub command: Option, + pub message: String, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)] +#[serde(rename_all = "snake_case")] +pub enum PermissionDenialReason { + RequestsDisabled, + UserDenied, + ProtectedBoundary, + ApprovalInsufficient, + GrantExpired, +} + #[derive(Clone, Debug, thiserror::Error)] pub enum ToolError { + #[error(transparent)] + PermissionDenied(Box), /// An ordinary tool failure with a known outcome. #[error("tool failed: {0}")] Failed(String), diff --git a/plugins/agent/native/src/owner/auxiliary.rs b/plugins/agent/native/src/owner/auxiliary.rs index 977adb4448f..275dff1cf9e 100644 --- a/plugins/agent/native/src/owner/auxiliary.rs +++ b/plugins/agent/native/src/owner/auxiliary.rs @@ -268,7 +268,10 @@ impl Auxiliary { } Err(Error::Admission(_)) | Err(Error::Tool( - ToolError::Failed(_) | ToolError::Native(_) | ToolError::Io { .. }, + ToolError::PermissionDenied(_) + | ToolError::Failed(_) + | ToolError::Native(_) + | ToolError::Io { .. }, )) => Outcome::Error, }; custody.journal().settle(outcome, timestamp()?).await.map_err(|error| { diff --git a/plugins/agent/native/src/owner/queue.rs b/plugins/agent/native/src/owner/queue.rs index ccbfc8a04a3..2d20164de43 100644 --- a/plugins/agent/native/src/owner/queue.rs +++ b/plugins/agent/native/src/owner/queue.rs @@ -549,6 +549,7 @@ impl Owner { }; let tool_error_variant = match error { RunError::Tool(error) => match error { + ToolError::PermissionDenied(_) => "permission_denied", ToolError::Failed(_) => "failed", ToolError::Native(_) => "native", ToolError::Io { .. } => "io", diff --git a/plugins/workspace/README.md b/plugins/workspace/README.md index 76fb864732c..2d8cbe8d4ee 100644 --- a/plugins/workspace/README.md +++ b/plugins/workspace/README.md @@ -30,3 +30,5 @@ Workspace services can be composed independently of the Agent engine. Each leaf - Computer Use exposes `cua_repl` and `cua_reset` through the [native provider](workspace-computer/native/src/lib.rs). Each conversation needs explicit access approval. Changing its permission boundary invalidates that grant; Explore mode cannot operate the computer. REPL variables and target bindings last until reset or Host shutdown, while the conversation grant survives restart. Desktop and browser connections are selected in the provider configuration. The shared [API](api/README.md) defines workspace contracts. [Support](support/README.md) handles configured roots, path resolution and private-path exclusions. A service receives its resource limits and authority explicitly; a path in request JSON cannot widen them. + +Files and Processes return `permission_denied` for a confirmed Host refusal before execution, preserving the target permissions, command and reason. The reasons distinguish disabled requests, explicit user denial, protected boundaries, insufficient approval and expired grants. Cancellation and observation failures are separate errors. Process status and exit evidence remain independent of permission reasons; stderr containing `EPERM` alone is not evidence of a sandbox refusal. diff --git a/plugins/workspace/README.zh-CN.md b/plugins/workspace/README.zh-CN.md index 814d83e5722..956e4267744 100644 --- a/plugins/workspace/README.zh-CN.md +++ b/plugins/workspace/README.zh-CN.md @@ -30,3 +30,5 @@ - Computer Use 通过[原生提供方](workspace-computer/native/src/lib.rs)提供 `cua_repl` 和 `cua_reset`。每个会话需要显式授权,权限边界变更会使授权失效;Explore 模式不能操作电脑。REPL 变量和目标绑定保留到重置或 Host 关闭,会话授权则在重启后保留。桌面与浏览器连接由提供方配置选择。 共用 [API](api/README.zh-CN.md)定义工作区契约,[support](support/README.zh-CN.md)处理已配置根目录、路径解析和私有路径排除。服务显式接收资源限制与权限,请求 JSON 中的路径不能扩大它们。 + +Files 与 Processes 对执行前确定的 Host 权限拒绝返回 `permission_denied`,保留目标权限、命令和原因,区分禁止申请、用户明确拒绝、保护边界、批准不足及授权失效。取消和观察失败使用独立错误。进程状态及退出证据与权限原因分开表达;仅 stderr 中出现 `EPERM` 不构成沙箱拒绝的证据。 diff --git a/plugins/workspace/api/src/files.rs b/plugins/workspace/api/src/files.rs index 861e6f92262..610a783a2b2 100644 --- a/plugins/workspace/api/src/files.rs +++ b/plugins/workspace/api/src/files.rs @@ -30,6 +30,11 @@ pub const EXPORT: &str = "files"; #[serde(tag = "kind", rename_all = "snake_case", deny_unknown_fields)] #[schemars(rename = "WorkspaceFileResult")] pub enum FileResult { + PermissionDenied { + #[serde(rename = "executionState")] + execution_state: crate::NotStarted, + denial: Box, + }, Completed { value: Value, }, diff --git a/plugins/workspace/api/src/lib.rs b/plugins/workspace/api/src/lib.rs index 486748a1502..3c42f938e35 100644 --- a/plugins/workspace/api/src/lib.rs +++ b/plugins/workspace/api/src/lib.rs @@ -24,3 +24,9 @@ pub mod files; pub mod processes; pub mod protocol; pub mod terminal; + +#[derive(serde::Serialize, schemars::JsonSchema)] +#[serde(rename_all = "snake_case")] +pub enum NotStarted { + NotStarted, +} diff --git a/plugins/workspace/api/src/processes.rs b/plugins/workspace/api/src/processes.rs index dbc5408ea5c..a4f75dcfc3b 100644 --- a/plugins/workspace/api/src/processes.rs +++ b/plugins/workspace/api/src/processes.rs @@ -58,8 +58,17 @@ pub fn configuration_schema() -> Value { #[serde(tag = "kind", rename_all = "snake_case", deny_unknown_fields)] #[schemars(rename = "WorkspaceProcessResult")] pub enum ProcessResult { - Completed { value: Value }, - Failed { message: String }, + PermissionDenied { + #[serde(rename = "executionState")] + execution_state: crate::NotStarted, + denial: Box, + }, + Completed { + value: Value, + }, + Failed { + message: String, + }, } #[derive(Deserialize, JsonSchema)] #[serde(deny_unknown_fields)] diff --git a/plugins/workspace/workspace-files/native/src/lib.rs b/plugins/workspace/workspace-files/native/src/lib.rs index cc4a80b546e..682831336a7 100644 --- a/plugins/workspace/workspace-files/native/src/lib.rs +++ b/plugins/workspace/workspace-files/native/src/lib.rs @@ -181,6 +181,11 @@ impl Handler for Files { Err(error) if name == "readImage" => { return Err(HandlerError::from(error.to_string())); } + Err(ToolError::PermissionDenied(denial)) => FileResult::PermissionDenied { + execution_state: maka_workspace_api::NotStarted::NotStarted, + denial, + }, + Err(ToolError::Failed(message) | ToolError::Io { message, .. }) => { FileResult::Failed { message } } diff --git a/plugins/workspace/workspace-files/native/src/permissions.rs b/plugins/workspace/workspace-files/native/src/permissions.rs index 2b770963818..65d63927c85 100644 --- a/plugins/workspace/workspace-files/native/src/permissions.rs +++ b/plugins/workspace/workspace-files/native/src/permissions.rs @@ -19,6 +19,7 @@ use super::*; use maka_agent_facts::interaction::PermissionRequest; +use maka_agent_facts::tools::PermissionDenialReason; use std::path::Component; pub(super) async fn authorize( @@ -56,7 +57,10 @@ pub(super) async fn authorize( network: Network::Denied, }; if !captured.ceiling.permits(&lexical).map_err(failed)? { - return Err(failed( + return Err(maka_agent_authority::permissions::denied( + PermissionDenialReason::ProtectedBoundary, + &lexical, + None, "Workspace metadata or Host-private files cannot be granted", )); } @@ -127,7 +131,10 @@ pub(super) async fn authorize( network: Network::Denied, }; if !captured.ceiling.permits(&permissions).map_err(failed)? { - return Err(failed( + return Err(maka_agent_authority::permissions::denied( + PermissionDenialReason::ProtectedBoundary, + &permissions, + None, "Workspace metadata or Host-private files cannot be granted", )); } @@ -165,7 +172,10 @@ pub(super) async fn authorize( .and_then(|sandbox| sandbox.intersect(&floor)) .map_err(failed)?; if !captured.sandbox.permits(&required).map_err(failed)? { - return Err(failed( + return Err(maka_agent_authority::permissions::denied( + PermissionDenialReason::ApprovalInsufficient, + &required, + None, "Not all target files were approved; no files were changed", )); } diff --git a/plugins/workspace/workspace-processes/native/src/execute.rs b/plugins/workspace/workspace-processes/native/src/execute.rs index 7fb043102c5..bbd429c9707 100644 --- a/plugins/workspace/workspace-processes/native/src/execute.rs +++ b/plugins/workspace/workspace-processes/native/src/execute.rs @@ -319,7 +319,10 @@ impl Processes { .map_err(failed)? .map_err(failed)?; if !captured.ceiling.permits(&required).map_err(failed)? { - return Err(failed( + return Err(maka_agent_authority::permissions::denied( + maka_agent_facts::tools::PermissionDenialReason::ProtectedBoundary, + &required, + requested.command.as_ref(), "Requested access exceeds the process installation or protected workspace boundary", )); } @@ -328,7 +331,7 @@ impl Processes { access, PermissionRequest { reason: requested.reason, - command: requested.command, + command: requested.command.clone(), permissions: required.clone(), }, access.target(), @@ -340,7 +343,10 @@ impl Processes { .map_err(failed)?; } if !sandbox.permits(&required).map_err(failed)? { - return Err(failed( + return Err(maka_agent_authority::permissions::denied( + maka_agent_facts::tools::PermissionDenialReason::ApprovalInsufficient, + &required, + requested.command.as_ref(), "Not all requested process permissions were approved; command was not started", )); } diff --git a/plugins/workspace/workspace-processes/native/src/lib.rs b/plugins/workspace/workspace-processes/native/src/lib.rs index 7f3659be75e..bc00e908034 100644 --- a/plugins/workspace/workspace-processes/native/src/lib.rs +++ b/plugins/workspace/workspace-processes/native/src/lib.rs @@ -266,6 +266,10 @@ impl Handler for Service { ticket.complete(cleanup); let result = match result { Ok(value) => ProcessResult::Completed { value }, + Err(ToolError::PermissionDenied(denial)) => ProcessResult::PermissionDenied { + execution_state: maka_workspace_api::NotStarted::NotStarted, + denial, + }, Err(ToolError::Failed(message) | ToolError::Io { message, .. }) => { ProcessResult::Failed { message } }