agentxchain 2.135.1 → 2.136.1

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.
@@ -4,10 +4,28 @@ function normalizeMcpTransport(runtime) {
4
4
  : 'stdio';
5
5
  }
6
6
 
7
+ const DECLARABLE_CAPABILITY_FIELDS = new Set([
8
+ 'can_write_files',
9
+ 'proposal_support',
10
+ 'workflow_artifact_ownership',
11
+ ]);
12
+
13
+ function mergeExplicitCapabilities(base, runtime) {
14
+ const declared = runtime?.capabilities;
15
+ if (!declared || typeof declared !== 'object' || Array.isArray(declared)) return base;
16
+ const merged = { ...base };
17
+ for (const field of DECLARABLE_CAPABILITY_FIELDS) {
18
+ if (typeof declared[field] === 'string' && declared[field].length > 0) {
19
+ merged[field] = declared[field];
20
+ }
21
+ }
22
+ return merged;
23
+ }
24
+
7
25
  export function getRuntimeCapabilityContract(runtime = {}) {
8
26
  switch (runtime?.type) {
9
27
  case 'manual':
10
- return {
28
+ return mergeExplicitCapabilities({
11
29
  runtime_type: 'manual',
12
30
  transport: 'manual',
13
31
  can_write_files: 'direct',
@@ -15,10 +33,10 @@ export function getRuntimeCapabilityContract(runtime = {}) {
15
33
  proposal_support: 'none',
16
34
  requires_local_binary: false,
17
35
  workflow_artifact_ownership: 'yes',
18
- };
36
+ }, runtime);
19
37
 
20
38
  case 'local_cli':
21
- return {
39
+ return mergeExplicitCapabilities({
22
40
  runtime_type: 'local_cli',
23
41
  transport: 'local_cli',
24
42
  can_write_files: 'direct',
@@ -26,10 +44,10 @@ export function getRuntimeCapabilityContract(runtime = {}) {
26
44
  proposal_support: 'optional',
27
45
  requires_local_binary: true,
28
46
  workflow_artifact_ownership: 'yes',
29
- };
47
+ }, runtime);
30
48
 
31
49
  case 'api_proxy':
32
- return {
50
+ return mergeExplicitCapabilities({
33
51
  runtime_type: 'api_proxy',
34
52
  transport: 'provider_api',
35
53
  can_write_files: 'proposal_only',
@@ -37,10 +55,10 @@ export function getRuntimeCapabilityContract(runtime = {}) {
37
55
  proposal_support: 'native',
38
56
  requires_local_binary: false,
39
57
  workflow_artifact_ownership: 'proposal_apply_required',
40
- };
58
+ }, runtime);
41
59
 
42
60
  case 'remote_agent':
43
- return {
61
+ return mergeExplicitCapabilities({
44
62
  runtime_type: 'remote_agent',
45
63
  transport: 'remote_http',
46
64
  can_write_files: 'proposal_only',
@@ -48,13 +66,13 @@ export function getRuntimeCapabilityContract(runtime = {}) {
48
66
  proposal_support: 'native',
49
67
  requires_local_binary: false,
50
68
  workflow_artifact_ownership: 'proposal_apply_required',
51
- };
69
+ }, runtime);
52
70
 
53
71
  case 'mcp': {
54
72
  const transport = normalizeMcpTransport(runtime) === 'streamable_http'
55
73
  ? 'mcp_streamable_http'
56
74
  : 'mcp_stdio';
57
- return {
75
+ return mergeExplicitCapabilities({
58
76
  runtime_type: 'mcp',
59
77
  transport,
60
78
  can_write_files: 'tool_defined',
@@ -62,11 +80,11 @@ export function getRuntimeCapabilityContract(runtime = {}) {
62
80
  proposal_support: 'tool_defined',
63
81
  requires_local_binary: transport === 'mcp_stdio',
64
82
  workflow_artifact_ownership: 'tool_defined',
65
- };
83
+ }, runtime);
66
84
  }
67
85
 
68
86
  default:
69
- return {
87
+ return mergeExplicitCapabilities({
70
88
  runtime_type: runtime?.type || 'unknown',
71
89
  transport: 'unknown',
72
90
  can_write_files: 'unknown',
@@ -74,7 +92,7 @@ export function getRuntimeCapabilityContract(runtime = {}) {
74
92
  proposal_support: 'unknown',
75
93
  requires_local_binary: false,
76
94
  workflow_artifact_ownership: 'unknown',
77
- };
95
+ }, runtime);
78
96
  }
79
97
  }
80
98
 
@@ -91,79 +109,69 @@ export function getRoleRuntimeCapabilityContract(roleId, role = {}, runtime = {}
91
109
  let workflowArtifactOwnership = 'unknown';
92
110
 
93
111
  if (authority === 'review_only') {
94
- switch (runtime?.type) {
95
- case 'manual':
96
- effectiveWritePath = 'planning_only';
97
- workflowArtifactOwnership = 'yes';
98
- appendNote(notes, 'Manual review roles can satisfy workflow-kit ownership for planning artifacts.');
99
- break;
100
- case 'local_cli':
101
- effectiveWritePath = 'invalid_review_only_binding';
102
- workflowArtifactOwnership = 'invalid';
103
- appendNote(notes, 'review_only + local_cli is invalid because local_cli exposes direct repo writes.');
104
- break;
105
- case 'api_proxy':
106
- case 'remote_agent':
107
- effectiveWritePath = 'review_artifact_only';
108
- workflowArtifactOwnership = 'no';
109
- appendNote(notes, 'Review-only remote turns can attest and produce review artifacts, not workflow-kit files.');
110
- break;
111
- case 'mcp':
112
- effectiveWritePath = 'tool_defined';
113
- workflowArtifactOwnership = 'tool_defined';
114
- appendNote(notes, 'MCP review-only file production depends on the configured tool, not runtime type alone.');
115
- break;
116
- default:
117
- effectiveWritePath = 'unknown';
118
- workflowArtifactOwnership = 'unknown';
119
- break;
112
+ if (runtime?.type === 'manual') {
113
+ effectiveWritePath = 'planning_only';
114
+ workflowArtifactOwnership = 'yes';
115
+ appendNote(notes, 'Manual review roles can satisfy workflow-kit ownership for planning artifacts.');
116
+ } else if (base.can_write_files === 'direct' && runtime?.type === 'local_cli') {
117
+ effectiveWritePath = 'invalid_review_only_binding';
118
+ workflowArtifactOwnership = 'invalid';
119
+ appendNote(notes, 'review_only + local_cli is invalid because local_cli exposes direct repo writes.');
120
+ } else if (base.can_write_files === 'tool_defined' || base.workflow_artifact_ownership === 'tool_defined') {
121
+ effectiveWritePath = 'tool_defined';
122
+ workflowArtifactOwnership = 'tool_defined';
123
+ appendNote(notes, 'Review-only file production depends on the configured tool contract, not runtime type alone.');
124
+ } else if (base.can_write_files === 'proposal_only') {
125
+ effectiveWritePath = 'review_artifact_only';
126
+ workflowArtifactOwnership = 'no';
127
+ appendNote(notes, 'Review-only remote turns can attest and produce review artifacts, not workflow-kit files.');
128
+ } else if (base.can_write_files === 'direct') {
129
+ effectiveWritePath = base.workflow_artifact_ownership === 'yes' ? 'planning_only' : 'review_artifact_only';
130
+ workflowArtifactOwnership = base.workflow_artifact_ownership === 'yes' ? 'yes' : 'no';
131
+ appendNote(notes, 'Review-only roles constrain direct-write runtimes to planning or review artifact production only.');
132
+ } else {
133
+ effectiveWritePath = 'unknown';
134
+ workflowArtifactOwnership = 'unknown';
120
135
  }
121
136
  } else if (authority === 'proposed') {
122
- switch (runtime?.type) {
123
- case 'manual':
124
- case 'local_cli':
125
- effectiveWritePath = 'patch_authoring';
126
- workflowArtifactOwnership = 'yes';
127
- appendNote(notes, 'This role can prepare patch-shaped work while still satisfying workflow-kit artifact ownership.');
128
- break;
129
- case 'api_proxy':
130
- case 'remote_agent':
131
- effectiveWritePath = 'proposal_apply_required';
132
- workflowArtifactOwnership = 'proposal_apply_required';
133
- appendNote(notes, 'Accepted proposals are staged under .agentxchain/proposed and require proposal apply before gate files exist in the repo.');
134
- break;
135
- case 'mcp':
136
- effectiveWritePath = 'tool_defined';
137
- workflowArtifactOwnership = 'tool_defined';
138
- appendNote(notes, 'MCP proposed-authoring behavior depends on the governed tool implementation.');
139
- break;
140
- default:
141
- effectiveWritePath = 'unknown';
142
- workflowArtifactOwnership = 'unknown';
143
- break;
137
+ if (runtime?.type === 'manual') {
138
+ effectiveWritePath = 'patch_authoring';
139
+ workflowArtifactOwnership = 'yes';
140
+ appendNote(notes, 'This role can prepare patch-shaped work while still satisfying workflow-kit artifact ownership.');
141
+ } else if (base.can_write_files === 'direct') {
142
+ effectiveWritePath = 'patch_authoring';
143
+ workflowArtifactOwnership = base.workflow_artifact_ownership;
144
+ appendNote(notes, 'This role can prepare patch-shaped work while still satisfying workflow-kit artifact ownership.');
145
+ } else if (base.can_write_files === 'proposal_only') {
146
+ effectiveWritePath = 'proposal_apply_required';
147
+ workflowArtifactOwnership = 'proposal_apply_required';
148
+ appendNote(notes, 'Accepted proposals are staged under .agentxchain/proposed and require proposal apply before gate files exist in the repo.');
149
+ } else if (base.can_write_files === 'tool_defined' || base.workflow_artifact_ownership === 'tool_defined') {
150
+ effectiveWritePath = 'tool_defined';
151
+ workflowArtifactOwnership = 'tool_defined';
152
+ appendNote(notes, 'Proposed-authoring behavior depends on the governed tool implementation.');
153
+ } else {
154
+ effectiveWritePath = 'unknown';
155
+ workflowArtifactOwnership = 'unknown';
144
156
  }
145
157
  } else if (authority === 'authoritative') {
146
- switch (runtime?.type) {
147
- case 'manual':
148
- case 'local_cli':
149
- effectiveWritePath = 'direct';
150
- workflowArtifactOwnership = 'yes';
151
- break;
152
- case 'api_proxy':
153
- case 'remote_agent':
154
- effectiveWritePath = 'invalid_authoritative_binding';
155
- workflowArtifactOwnership = 'invalid';
156
- appendNote(notes, `${runtime.type} does not support authoritative roles in v1.`);
157
- break;
158
- case 'mcp':
159
- effectiveWritePath = 'tool_defined';
160
- workflowArtifactOwnership = 'tool_defined';
161
- appendNote(notes, 'MCP authoritative repo writes are tool-defined, not guaranteed by runtime type.');
162
- break;
163
- default:
164
- effectiveWritePath = 'unknown';
165
- workflowArtifactOwnership = 'unknown';
166
- break;
158
+ if (base.can_write_files === 'direct') {
159
+ effectiveWritePath = 'direct';
160
+ workflowArtifactOwnership = base.workflow_artifact_ownership;
161
+ if (runtime?.type === 'mcp') {
162
+ appendNote(notes, 'Authoritative MCP repo writes are accepted because the connector declared a direct write path.');
163
+ }
164
+ } else if (base.can_write_files === 'proposal_only') {
165
+ effectiveWritePath = 'invalid_authoritative_binding';
166
+ workflowArtifactOwnership = 'invalid';
167
+ appendNote(notes, `${runtime.type} does not support authoritative roles in v1.`);
168
+ } else if (base.can_write_files === 'tool_defined' || base.workflow_artifact_ownership === 'tool_defined') {
169
+ effectiveWritePath = 'tool_defined';
170
+ workflowArtifactOwnership = 'tool_defined';
171
+ appendNote(notes, 'Authoritative repo writes are tool-defined, not guaranteed by runtime type alone.');
172
+ } else {
173
+ effectiveWritePath = 'unknown';
174
+ workflowArtifactOwnership = 'unknown';
167
175
  }
168
176
  }
169
177
 
@@ -213,6 +221,18 @@ export function summarizeRuntimeCapabilityContract(contract) {
213
221
  ].join('; ');
214
222
  }
215
223
 
224
+ export { DECLARABLE_CAPABILITY_FIELDS };
225
+
226
+ export function getCapabilityDeclarationWarnings(runtime = {}) {
227
+ const warnings = [];
228
+ const declared = runtime?.capabilities;
229
+ if (!declared || typeof declared !== 'object' || Array.isArray(declared)) return warnings;
230
+ if (declared.can_write_files === 'direct' && (runtime.type === 'api_proxy' || runtime.type === 'remote_agent')) {
231
+ warnings.push(`Runtime type "${runtime.type}" declares can_write_files=direct, which the reference runner does not support in v1. A third-party runner may.`);
232
+ }
233
+ return warnings;
234
+ }
235
+
216
236
  export function summarizeRoleRuntimeCapability(contract) {
217
237
  return [
218
238
  `${contract.role_id}(${contract.role_write_authority})`,
@@ -220,3 +240,34 @@ export function summarizeRoleRuntimeCapability(contract) {
220
240
  `owned_by=${contract.workflow_artifact_ownership}`,
221
241
  ].join('; ');
222
242
  }
243
+
244
+ export function buildRuntimeCapabilityReport(runtimeId, runtime, roles) {
245
+ const mergedContract = getRuntimeCapabilityContract(runtime);
246
+ const declaredCapabilities = (runtime?.capabilities && typeof runtime.capabilities === 'object' && !Array.isArray(runtime.capabilities))
247
+ ? { ...runtime.capabilities }
248
+ : {};
249
+ const declarationWarnings = getCapabilityDeclarationWarnings(runtime);
250
+
251
+ const roleBindings = [];
252
+ for (const [roleId, role] of Object.entries(roles || {})) {
253
+ const boundRuntime = role.runtime_id || role.runtime;
254
+ if (boundRuntime !== runtimeId) continue;
255
+ const roleContract = getRoleRuntimeCapabilityContract(roleId, role, runtime);
256
+ roleBindings.push({
257
+ role_id: roleContract.role_id,
258
+ role_write_authority: roleContract.role_write_authority,
259
+ effective_write_path: roleContract.effective_write_path,
260
+ workflow_artifact_ownership: roleContract.workflow_artifact_ownership,
261
+ notes: roleContract.notes,
262
+ });
263
+ }
264
+
265
+ return {
266
+ runtime_id: runtimeId,
267
+ runtime_type: runtime?.type || 'unknown',
268
+ declared_capabilities: declaredCapabilities,
269
+ merged_contract: mergedContract,
270
+ declaration_warnings: declarationWarnings,
271
+ role_bindings: roleBindings,
272
+ };
273
+ }