vgxness 1.20.3 → 1.20.5

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.
@@ -50,6 +50,11 @@ export const SUPPORTED_VGX_MCP_TOOL_NAMES = [
50
50
  'vgxness_run_list',
51
51
  'vgxness_run_get',
52
52
  'vgxness_run_preflight',
53
+ 'vgxness_run_task_grant_create',
54
+ 'vgxness_run_task_grant_list',
55
+ 'vgxness_run_task_grant_get',
56
+ 'vgxness_run_task_grant_revoke',
57
+ 'vgxness_run_task_grant_preview_match',
53
58
  'vgxness_run_start',
54
59
  'vgxness_run_checkpoint',
55
60
  'vgxness_run_finalize',
@@ -108,6 +113,11 @@ export const EXPOSED_VGX_MCP_TOOL_NAMES = [
108
113
  'run_list',
109
114
  'run_get',
110
115
  'run_preflight',
116
+ 'run_task_grant_create',
117
+ 'run_task_grant_list',
118
+ 'run_task_grant_get',
119
+ 'run_task_grant_revoke',
120
+ 'run_task_grant_preview_match',
111
121
  'run_start',
112
122
  'run_checkpoint',
113
123
  'run_finalize',
@@ -132,6 +142,8 @@ const agentModes = ['agent', 'subagent'];
132
142
  const memoryTypes = ['architecture', 'decision', 'bugfix', 'pattern', 'config', 'discovery', 'learning', 'preference', 'manual'];
133
143
  const activityKinds = ['prompt', 'tool_call', 'artifact', 'summary', 'error'];
134
144
  const runStatuses = ['created', 'planned', 'running', 'needs-human', 'completed', 'failed', 'blocked', 'cancelled'];
145
+ const taskGrantStatuses = ['active', 'revoked', 'expired', 'exhausted'];
146
+ const taskGrantCategories = ['shell', 'test-run', 'git'];
135
147
  const finalRunStatuses = ['completed', 'failed', 'blocked', 'cancelled'];
136
148
  const runOutcomes = ['success', 'partial', 'failure', 'blocked', 'cancelled'];
137
149
  const payloadModes = ['compact', 'verbose'];
@@ -142,6 +154,63 @@ const skillSourceKinds = ['path', 'url', 'inline'];
142
154
  const skillUsageOutcomes = ['selected', 'injected', 'helped', 'failed', 'neutral'];
143
155
  const skillAttachmentTargetTypes = ['agent', 'subagent', 'workflow-phase', 'provider-adapter', 'intent-signal'];
144
156
  const jsonRecordSchema = z.record(z.string(), z.unknown());
157
+ const explicitRequestInputSchema = z
158
+ .object({
159
+ version: z.literal(1),
160
+ actor: z
161
+ .object({
162
+ type: z.literal('human'),
163
+ id: z.string().min(1).optional(),
164
+ displayName: z.string().min(1).optional(),
165
+ })
166
+ .strict(),
167
+ request: z
168
+ .object({
169
+ id: z.string().min(1).optional(),
170
+ source: z.enum(['chat', 'mcp', 'cli', 'provider', 'unknown']),
171
+ utteranceRef: z.string().min(1).optional(),
172
+ excerpt: z.string().min(1).optional(),
173
+ requestedAt: z.string().min(1),
174
+ sessionId: z.string().min(1).optional(),
175
+ runId: z.string().min(1).optional(),
176
+ })
177
+ .strict(),
178
+ requestedOperation: z
179
+ .object({
180
+ category: z.enum(permissionCategories),
181
+ operation: z.string().min(1),
182
+ commandId: z.string().min(1).optional(),
183
+ providerToolName: z.string().min(1).optional(),
184
+ targetPath: z.string().min(1).optional(),
185
+ targetPaths: z.array(z.string().min(1)).optional(),
186
+ cwd: z.string().min(1).optional(),
187
+ workspaceRoot: z.string().min(1).optional(),
188
+ scopeSummary: z.string().min(1),
189
+ })
190
+ .strict(),
191
+ riskAttributes: z
192
+ .object({
193
+ destructive: z.boolean().optional(),
194
+ privileged: z.boolean().optional(),
195
+ external: z.boolean().optional(),
196
+ ambiguous: z.boolean().optional(),
197
+ secrets: z.boolean().optional(),
198
+ providerConfigMutation: z.boolean().optional(),
199
+ globalConfigMutation: z.boolean().optional(),
200
+ network: z.boolean().optional(),
201
+ install: z.boolean().optional(),
202
+ })
203
+ .strict(),
204
+ parser: z
205
+ .object({
206
+ exact: z.boolean(),
207
+ confidence: z.enum(['high', 'medium', 'low']).optional(),
208
+ ambiguityReasons: z.array(z.string().min(1)).optional(),
209
+ expansionReasons: z.array(z.string().min(1)).optional(),
210
+ })
211
+ .strict(),
212
+ })
213
+ .strict();
145
214
  const skillLifecycleActorSchema = z
146
215
  .object({
147
216
  type: z.enum(['human', 'agent', 'system']),
@@ -163,6 +232,26 @@ const providerChangePlanTypes = ['opencode-mcp-install', 'claude-mcp-install', '
163
232
  const sddPhaseInputSchema = z.union([z.enum(sddPhases), z.literal('apply')]);
164
233
  const sddPhaseSchema = z.enum(sddPhases);
165
234
  const jsonValueSchema = z.lazy(() => z.union([z.string(), z.number().finite(), z.boolean(), z.null(), z.array(jsonValueSchema), z.record(z.string(), jsonValueSchema)]));
235
+ const taskGrantHumanActorInputSchema = z
236
+ .object({
237
+ type: z.literal('human'),
238
+ id: z.string().min(1),
239
+ displayName: z.string().min(1).optional(),
240
+ })
241
+ .strict();
242
+ const taskGrantRevocationActorInputSchema = z
243
+ .object({
244
+ type: z.enum(['human', 'system']),
245
+ id: z.string().min(1),
246
+ displayName: z.string().min(1).optional(),
247
+ })
248
+ .strict();
249
+ const taskGrantCommandPolicyInputSchema = z
250
+ .object({
251
+ kind: z.literal('allowlisted-command-ids'),
252
+ ids: z.array(z.string().min(1)).min(1),
253
+ })
254
+ .strict();
166
255
  const providerEvidenceOutputSchema = z
167
256
  .object({
168
257
  staticManifestKnown: z.boolean(),
@@ -810,6 +899,10 @@ export const VGX_MCP_TOOL_INPUT_SCHEMAS = {
810
899
  agentId: z.string().min(1).optional(),
811
900
  workspaceRoot: z.string().min(1).optional(),
812
901
  targetPath: z.string().min(1).optional(),
902
+ taskFingerprint: z.string().min(1).optional(),
903
+ commandId: z.string().min(1).optional(),
904
+ cwd: z.string().min(1).optional(),
905
+ targetPaths: z.array(z.string().min(1)).optional(),
813
906
  providerToolName: z.string().min(1).optional(),
814
907
  agent: z
815
908
  .object({
@@ -824,8 +917,59 @@ export const VGX_MCP_TOOL_INPUT_SCHEMAS = {
824
917
  external: z.boolean().optional(),
825
918
  privileged: z.boolean().optional(),
826
919
  ambiguous: z.boolean().optional(),
920
+ providerConfigMutation: z.boolean().optional(),
921
+ globalConfigMutation: z.boolean().optional(),
922
+ explicitRequest: explicitRequestInputSchema.optional(),
827
923
  })
828
924
  .passthrough(),
925
+ vgxness_run_task_grant_create: z
926
+ .object({
927
+ runId: z.string().min(1),
928
+ agentId: z.string().min(1),
929
+ taskFingerprint: z.string().min(1),
930
+ categories: z.array(z.enum(taskGrantCategories)).min(1),
931
+ externalRoots: z.array(z.string().min(1)).min(1),
932
+ cwdPatterns: z.array(z.string().min(1)).optional(),
933
+ commandPolicy: taskGrantCommandPolicyInputSchema,
934
+ expiresAt: z.string().min(1),
935
+ maxUses: z.number().int().positive(),
936
+ createdBy: taskGrantHumanActorInputSchema,
937
+ creationReason: z.string().min(1),
938
+ approvedScopeSummary: z.string().min(1),
939
+ })
940
+ .strict(),
941
+ vgxness_run_task_grant_list: z
942
+ .object({
943
+ runId: z.string().min(1).optional(),
944
+ agentId: z.string().min(1).optional(),
945
+ status: z.enum(taskGrantStatuses).optional(),
946
+ limit: z.number().int().min(1).max(100).optional(),
947
+ })
948
+ .strict(),
949
+ vgxness_run_task_grant_get: z
950
+ .object({
951
+ grantId: z.string().min(1),
952
+ })
953
+ .strict(),
954
+ vgxness_run_task_grant_revoke: z
955
+ .object({
956
+ grantId: z.string().min(1),
957
+ actor: taskGrantRevocationActorInputSchema,
958
+ reason: z.string().min(1),
959
+ })
960
+ .strict(),
961
+ vgxness_run_task_grant_preview_match: z
962
+ .object({
963
+ runId: z.string().min(1),
964
+ agentId: z.string().min(1).optional(),
965
+ taskFingerprint: z.string().min(1),
966
+ category: z.enum(permissionCategories),
967
+ operation: z.string().min(1),
968
+ workspaceRoot: z.string().min(1).optional(),
969
+ cwd: z.string().min(1),
970
+ targetPaths: z.array(z.string().min(1)).optional(),
971
+ })
972
+ .strict(),
829
973
  vgxness_run_start: z
830
974
  .object({
831
975
  project: z.string().min(1),
@@ -93,6 +93,12 @@ function descriptionForTool(publicToolName) {
93
93
  return 'Human-only mutating SDD reopen gate; records an explicit human decision to reopen an artifact and must not be called by agents on their own behalf. Use only after human instruction, then revise or re-review the phase.';
94
94
  if (publicToolName === 'run_preflight')
95
95
  return 'Agent-callable advisory/planning-only run preflight; evaluates permissions, records a plan, and returns a workspace strategy recommendation, but does not execute the checked shell/git/install/network/provider/secrets operation and does not create branches, worktrees, or pull requests. Use it to decide whether human approval or a separate executor step is required.';
96
+ if (publicToolName === 'run_task_grant_create')
97
+ return 'Human-approved lifecycle tool that creates a task-scoped command grant for bounded allowlisted external command operations.';
98
+ if (publicToolName === 'run_task_grant_list' || publicToolName === 'run_task_grant_get' || publicToolName === 'run_task_grant_preview_match')
99
+ return 'Read-only task-scoped command grant lifecycle tool; does not execute commands, write provider config, or consume grant usage.';
100
+ if (publicToolName === 'run_task_grant_revoke')
101
+ return 'Revokes a task-scoped command grant with an explicit human or system actor and reason.';
96
102
  const toolName = toInternalVgxMcpToolName(publicToolName);
97
103
  return `VGX control-plane tool ${toolName}`;
98
104
  }
@@ -2,6 +2,7 @@ import { isRiskyPermissionCategory, permissionCategories } from '../permissions/
2
2
  import { parseOperationRetryPolicy } from '../runs/operation-retry.js';
3
3
  import { normalizeSddPhaseInput, sddPhases } from '../sdd/schema.js';
4
4
  import { workflowIds } from '../workflows/schema.js';
5
+ import { commandAllowlistIds } from '../workflows/command-allowlist-adapter.js';
5
6
  import { supportedTypeMessage, verificationChangeTypes } from '../verification/index.js';
6
7
  import { errorEnvelope, isVgxMcpToolName, } from './schema.js';
7
8
  const scopes = ['project', 'personal'];
@@ -9,6 +10,8 @@ const agentModes = ['agent', 'subagent'];
9
10
  const memoryTypes = ['architecture', 'decision', 'bugfix', 'pattern', 'config', 'discovery', 'learning', 'preference', 'manual'];
10
11
  const activityKinds = ['prompt', 'tool_call', 'artifact', 'summary', 'error'];
11
12
  const runStatuses = ['created', 'planned', 'running', 'needs-human', 'completed', 'failed', 'blocked', 'cancelled'];
13
+ const taskGrantStatuses = ['active', 'revoked', 'expired', 'exhausted'];
14
+ const taskGrantCategories = ['shell', 'test-run', 'git'];
12
15
  const finalRunStatuses = ['completed', 'failed', 'blocked', 'cancelled'];
13
16
  const runOutcomes = ['success', 'partial', 'failure', 'blocked', 'cancelled'];
14
17
  const permissionDecisions = ['allow', 'ask', 'deny'];
@@ -132,6 +135,16 @@ export function validateVgxMcpToolCall(call) {
132
135
  return validationSuccess(tool.value, validateRunGetInput(input, tool.value));
133
136
  case 'vgxness_run_preflight':
134
137
  return validationSuccess(tool.value, validateRunPreflightInput(input, tool.value));
138
+ case 'vgxness_run_task_grant_create':
139
+ return validationSuccess(tool.value, validateRunTaskGrantCreateInput(input, tool.value));
140
+ case 'vgxness_run_task_grant_list':
141
+ return validationSuccess(tool.value, validateRunTaskGrantListInput(input, tool.value));
142
+ case 'vgxness_run_task_grant_get':
143
+ return validationSuccess(tool.value, validateRunTaskGrantGetInput(input, tool.value));
144
+ case 'vgxness_run_task_grant_revoke':
145
+ return validationSuccess(tool.value, validateRunTaskGrantRevokeInput(input, tool.value));
146
+ case 'vgxness_run_task_grant_preview_match':
147
+ return validationSuccess(tool.value, validateRunTaskGrantPreviewMatchInput(input, tool.value));
135
148
  case 'vgxness_run_start':
136
149
  return validationSuccess(tool.value, validateRunStartInput(input, tool.value));
137
150
  case 'vgxness_run_checkpoint':
@@ -1565,6 +1578,10 @@ function validateRunPreflightInput(input, tool) {
1565
1578
  'agentId',
1566
1579
  'workspaceRoot',
1567
1580
  'targetPath',
1581
+ 'taskFingerprint',
1582
+ 'commandId',
1583
+ 'cwd',
1584
+ 'targetPaths',
1568
1585
  'providerToolName',
1569
1586
  'agent',
1570
1587
  'sandboxStrategy',
@@ -1572,6 +1589,9 @@ function validateRunPreflightInput(input, tool) {
1572
1589
  'external',
1573
1590
  'privileged',
1574
1591
  'ambiguous',
1592
+ 'providerConfigMutation',
1593
+ 'globalConfigMutation',
1594
+ 'explicitRequest',
1575
1595
  ]);
1576
1596
  if (!record.ok)
1577
1597
  return record;
@@ -1590,15 +1610,20 @@ function validateRunPreflightInput(input, tool) {
1590
1610
  return workflow;
1591
1611
  if (workflow.value !== undefined)
1592
1612
  result.workflow = workflow.value;
1593
- const copied = copyOptionalStrings(result, record.value, tool, ['phase', 'agentId', 'workspaceRoot', 'targetPath', 'providerToolName']);
1613
+ const copied = copyOptionalStrings(result, record.value, tool, ['phase', 'agentId', 'workspaceRoot', 'targetPath', 'taskFingerprint', 'commandId', 'cwd', 'providerToolName']);
1594
1614
  if (!copied.ok)
1595
1615
  return copied;
1616
+ const targetPaths = readOptionalStringArray(record.value, 'targetPaths', tool);
1617
+ if (!targetPaths.ok)
1618
+ return targetPaths;
1619
+ if (targetPaths.value !== undefined)
1620
+ result.targetPaths = targetPaths.value;
1596
1621
  const sandboxStrategy = readOptionalOneOf(record.value, 'sandboxStrategy', ['worktree'], tool);
1597
1622
  if (!sandboxStrategy.ok)
1598
1623
  return sandboxStrategy;
1599
1624
  if (sandboxStrategy.value !== undefined)
1600
1625
  result.sandboxStrategy = sandboxStrategy.value;
1601
- const booleans = copyOptionalBooleans(result, record.value, tool, ['destructive', 'external', 'privileged', 'ambiguous']);
1626
+ const booleans = copyOptionalBooleans(result, record.value, tool, ['destructive', 'external', 'privileged', 'ambiguous', 'providerConfigMutation', 'globalConfigMutation']);
1602
1627
  if (!booleans.ok)
1603
1628
  return booleans;
1604
1629
  if (record.value.agent !== undefined) {
@@ -1607,6 +1632,12 @@ function validateRunPreflightInput(input, tool) {
1607
1632
  return agent;
1608
1633
  result.agent = agent.value;
1609
1634
  }
1635
+ if (record.value.explicitRequest !== undefined) {
1636
+ const explicitRequest = readExplicitRequest(record.value.explicitRequest, tool);
1637
+ if (!explicitRequest.ok)
1638
+ return explicitRequest;
1639
+ result.explicitRequest = explicitRequest.value;
1640
+ }
1610
1641
  if (isRiskyPermissionCategory(result.category)) {
1611
1642
  if (result.phase !== undefined && result.agentId === undefined && result.agent === undefined) {
1612
1643
  return validationFailure(`VGX-managed preflight for risky category ${result.category} requires agentId when phase metadata is provided`, tool);
@@ -1617,6 +1648,270 @@ function validateRunPreflightInput(input, tool) {
1617
1648
  }
1618
1649
  return { ok: true, value: result };
1619
1650
  }
1651
+ function readExplicitRequest(value, tool) {
1652
+ const record = asRecord(value, tool, 'explicitRequest must be an object');
1653
+ if (!record.ok)
1654
+ return record;
1655
+ const unexpected = Object.keys(record.value).find((key) => !['version', 'actor', 'request', 'requestedOperation', 'riskAttributes', 'parser'].includes(key));
1656
+ if (unexpected !== undefined)
1657
+ return validationFailure(`Unexpected explicitRequest field: ${unexpected}`, tool);
1658
+ if (record.value.version !== 1)
1659
+ return validationFailure('explicitRequest.version must be 1', tool);
1660
+ const actor = readExplicitRequestActor(record.value.actor, tool);
1661
+ if (!actor.ok)
1662
+ return actor;
1663
+ const request = readExplicitRequestRequest(record.value.request, tool);
1664
+ if (!request.ok)
1665
+ return request;
1666
+ const requestedOperation = readExplicitRequestedOperation(record.value.requestedOperation, tool);
1667
+ if (!requestedOperation.ok)
1668
+ return requestedOperation;
1669
+ const riskAttributes = readExplicitRiskAttributes(record.value.riskAttributes, tool);
1670
+ if (!riskAttributes.ok)
1671
+ return riskAttributes;
1672
+ const parser = readExplicitParser(record.value.parser, tool);
1673
+ if (!parser.ok)
1674
+ return parser;
1675
+ return { ok: true, value: { version: 1, actor: actor.value, request: request.value, requestedOperation: requestedOperation.value, riskAttributes: riskAttributes.value, parser: parser.value } };
1676
+ }
1677
+ function readExplicitRequestActor(value, tool) {
1678
+ const record = asRecord(value, tool, 'explicitRequest.actor must be an object');
1679
+ if (!record.ok)
1680
+ return record;
1681
+ const unexpected = Object.keys(record.value).find((key) => !['type', 'id', 'displayName'].includes(key));
1682
+ if (unexpected !== undefined)
1683
+ return validationFailure(`Unexpected explicitRequest.actor field: ${unexpected}`, tool);
1684
+ const type = readRequiredOneOf(record.value, 'type', ['human'], tool);
1685
+ if (!type.ok)
1686
+ return type;
1687
+ const actor = { type: type.value };
1688
+ const copied = copyOptionalStrings(actor, record.value, tool, ['id', 'displayName']);
1689
+ if (!copied.ok)
1690
+ return copied;
1691
+ return { ok: true, value: actor };
1692
+ }
1693
+ function readExplicitRequestRequest(value, tool) {
1694
+ const record = asRecord(value, tool, 'explicitRequest.request must be an object');
1695
+ if (!record.ok)
1696
+ return record;
1697
+ const unexpected = Object.keys(record.value).find((key) => !['id', 'source', 'utteranceRef', 'excerpt', 'requestedAt', 'sessionId', 'runId'].includes(key));
1698
+ if (unexpected !== undefined)
1699
+ return validationFailure(`Unexpected explicitRequest.request field: ${unexpected}`, tool);
1700
+ const source = readRequiredOneOf(record.value, 'source', ['chat', 'mcp', 'cli', 'provider', 'unknown'], tool);
1701
+ if (!source.ok)
1702
+ return source;
1703
+ const requestedAt = readNonEmptyString(record.value, 'requestedAt', tool);
1704
+ if (!requestedAt.ok)
1705
+ return requestedAt;
1706
+ if (!validIsoTimestamp(requestedAt.value))
1707
+ return validationFailure('explicitRequest.request.requestedAt must be a valid ISO timestamp', tool);
1708
+ const request = { source: source.value, requestedAt: requestedAt.value };
1709
+ const copied = copyOptionalStrings(request, record.value, tool, ['id', 'utteranceRef', 'excerpt', 'sessionId', 'runId']);
1710
+ if (!copied.ok)
1711
+ return copied;
1712
+ return { ok: true, value: request };
1713
+ }
1714
+ function readExplicitRequestedOperation(value, tool) {
1715
+ const record = asRecord(value, tool, 'explicitRequest.requestedOperation must be an object');
1716
+ if (!record.ok)
1717
+ return record;
1718
+ const unexpected = Object.keys(record.value).find((key) => !['category', 'operation', 'commandId', 'providerToolName', 'targetPath', 'targetPaths', 'cwd', 'workspaceRoot', 'scopeSummary'].includes(key));
1719
+ if (unexpected !== undefined)
1720
+ return validationFailure(`Unexpected explicitRequest.requestedOperation field: ${unexpected}`, tool);
1721
+ const category = readRequiredOneOf(record.value, 'category', permissionCategories, tool);
1722
+ if (!category.ok)
1723
+ return category;
1724
+ const operation = readNonEmptyString(record.value, 'operation', tool);
1725
+ if (!operation.ok)
1726
+ return operation;
1727
+ const scopeSummary = readNonEmptyString(record.value, 'scopeSummary', tool);
1728
+ if (!scopeSummary.ok)
1729
+ return scopeSummary;
1730
+ const requestedOperation = { category: category.value, operation: operation.value, scopeSummary: scopeSummary.value };
1731
+ const copied = copyOptionalStrings(requestedOperation, record.value, tool, ['commandId', 'providerToolName', 'targetPath', 'cwd', 'workspaceRoot']);
1732
+ if (!copied.ok)
1733
+ return copied;
1734
+ const targetPaths = readOptionalStringArray(record.value, 'targetPaths', tool);
1735
+ if (!targetPaths.ok)
1736
+ return targetPaths;
1737
+ if (targetPaths.value !== undefined)
1738
+ requestedOperation.targetPaths = targetPaths.value;
1739
+ return { ok: true, value: requestedOperation };
1740
+ }
1741
+ function readExplicitRiskAttributes(value, tool) {
1742
+ const record = asRecord(value, tool, 'explicitRequest.riskAttributes must be an object');
1743
+ if (!record.ok)
1744
+ return record;
1745
+ const fields = ['destructive', 'privileged', 'external', 'ambiguous', 'secrets', 'providerConfigMutation', 'globalConfigMutation', 'network', 'install'];
1746
+ const unexpected = Object.keys(record.value).find((key) => !fields.includes(key));
1747
+ if (unexpected !== undefined)
1748
+ return validationFailure(`Unexpected explicitRequest.riskAttributes field: ${unexpected}`, tool);
1749
+ const riskAttributes = {};
1750
+ const copied = copyOptionalBooleans(riskAttributes, record.value, tool, fields);
1751
+ if (!copied.ok)
1752
+ return copied;
1753
+ return { ok: true, value: riskAttributes };
1754
+ }
1755
+ function readExplicitParser(value, tool) {
1756
+ const record = asRecord(value, tool, 'explicitRequest.parser must be an object');
1757
+ if (!record.ok)
1758
+ return record;
1759
+ const unexpected = Object.keys(record.value).find((key) => !['exact', 'confidence', 'ambiguityReasons', 'expansionReasons'].includes(key));
1760
+ if (unexpected !== undefined)
1761
+ return validationFailure(`Unexpected explicitRequest.parser field: ${unexpected}`, tool);
1762
+ if (typeof record.value.exact !== 'boolean')
1763
+ return validationFailure('explicitRequest.parser.exact must be a boolean', tool);
1764
+ const parser = { exact: record.value.exact };
1765
+ const confidence = readOptionalOneOf(record.value, 'confidence', ['high', 'medium', 'low'], tool);
1766
+ if (!confidence.ok)
1767
+ return confidence;
1768
+ if (confidence.value !== undefined)
1769
+ parser.confidence = confidence.value;
1770
+ const ambiguityReasons = readOptionalStringArray(record.value, 'ambiguityReasons', tool);
1771
+ if (!ambiguityReasons.ok)
1772
+ return ambiguityReasons;
1773
+ if (ambiguityReasons.value !== undefined)
1774
+ parser.ambiguityReasons = ambiguityReasons.value;
1775
+ const expansionReasons = readOptionalStringArray(record.value, 'expansionReasons', tool);
1776
+ if (!expansionReasons.ok)
1777
+ return expansionReasons;
1778
+ if (expansionReasons.value !== undefined)
1779
+ parser.expansionReasons = expansionReasons.value;
1780
+ return { ok: true, value: parser };
1781
+ }
1782
+ function validIsoTimestamp(value) {
1783
+ const timestamp = Date.parse(value);
1784
+ return Number.isFinite(timestamp) && new Date(timestamp).toISOString() === value;
1785
+ }
1786
+ function validateRunTaskGrantCreateInput(input, tool) {
1787
+ const record = inputRecord(input, tool, [
1788
+ 'runId',
1789
+ 'agentId',
1790
+ 'taskFingerprint',
1791
+ 'categories',
1792
+ 'externalRoots',
1793
+ 'cwdPatterns',
1794
+ 'commandPolicy',
1795
+ 'expiresAt',
1796
+ 'maxUses',
1797
+ 'createdBy',
1798
+ 'creationReason',
1799
+ 'approvedScopeSummary',
1800
+ ]);
1801
+ if (!record.ok)
1802
+ return record;
1803
+ const result = {};
1804
+ const copied = copyRequiredStrings(result, record.value, tool, ['runId', 'agentId', 'taskFingerprint', 'expiresAt', 'creationReason', 'approvedScopeSummary']);
1805
+ if (!copied.ok)
1806
+ return copied;
1807
+ const categories = readRequiredOneOfArray(record.value, 'categories', taskGrantCategories, tool);
1808
+ if (!categories.ok)
1809
+ return categories;
1810
+ result.categories = categories.value;
1811
+ const externalRoots = readRequiredStringArray(record.value, 'externalRoots', tool);
1812
+ if (!externalRoots.ok)
1813
+ return externalRoots;
1814
+ result.externalRoots = externalRoots.value;
1815
+ const cwdPatterns = readOptionalStringArray(record.value, 'cwdPatterns', tool);
1816
+ if (!cwdPatterns.ok)
1817
+ return cwdPatterns;
1818
+ if (cwdPatterns.value !== undefined)
1819
+ result.cwdPatterns = cwdPatterns.value;
1820
+ const commandPolicy = readTaskGrantCommandPolicy(record.value.commandPolicy, tool);
1821
+ if (!commandPolicy.ok)
1822
+ return commandPolicy;
1823
+ result.commandPolicy = commandPolicy.value;
1824
+ const createdBy = readTaskGrantActor(record.value.createdBy, tool, ['human']);
1825
+ if (!createdBy.ok)
1826
+ return createdBy;
1827
+ result.createdBy = createdBy.value;
1828
+ if (typeof record.value.maxUses !== 'number' || !Number.isSafeInteger(record.value.maxUses) || record.value.maxUses < 1)
1829
+ return validationFailure('maxUses must be a positive safe integer', tool);
1830
+ result.maxUses = record.value.maxUses;
1831
+ const expiresAt = Date.parse(result.expiresAt ?? '');
1832
+ if (!Number.isFinite(expiresAt) || new Date(expiresAt).toISOString() !== result.expiresAt)
1833
+ return validationFailure('expiresAt must be a valid ISO date', tool);
1834
+ const expiryDelta = expiresAt - Date.now();
1835
+ if (expiryDelta <= 0)
1836
+ return validationFailure('expiresAt must be in the future', tool);
1837
+ if (expiryDelta > 24 * 60 * 60 * 1000)
1838
+ return validationFailure('expiresAt must be within 24 hours', tool);
1839
+ return { ok: true, value: result };
1840
+ }
1841
+ function validateRunTaskGrantListInput(input, tool) {
1842
+ const record = inputRecord(input, tool, ['runId', 'agentId', 'status', 'limit']);
1843
+ if (!record.ok)
1844
+ return record;
1845
+ const result = {};
1846
+ const copied = copyOptionalStrings(result, record.value, tool, ['runId', 'agentId']);
1847
+ if (!copied.ok)
1848
+ return copied;
1849
+ const status = readOptionalOneOf(record.value, 'status', taskGrantStatuses, tool);
1850
+ if (!status.ok)
1851
+ return status;
1852
+ if (status.value !== undefined)
1853
+ result.status = status.value;
1854
+ const limit = readOptionalBoundedLimit(record.value, tool);
1855
+ if (!limit.ok)
1856
+ return limit;
1857
+ if (limit.value !== undefined)
1858
+ result.limit = limit.value;
1859
+ return { ok: true, value: result };
1860
+ }
1861
+ function validateRunTaskGrantGetInput(input, tool) {
1862
+ const record = inputRecord(input, tool, ['grantId']);
1863
+ if (!record.ok)
1864
+ return record;
1865
+ const grantId = readNonEmptyString(record.value, 'grantId', tool);
1866
+ if (!grantId.ok)
1867
+ return grantId;
1868
+ return { ok: true, value: { grantId: grantId.value } };
1869
+ }
1870
+ function validateRunTaskGrantRevokeInput(input, tool) {
1871
+ const record = inputRecord(input, tool, ['grantId', 'actor', 'reason']);
1872
+ if (!record.ok)
1873
+ return record;
1874
+ const grantId = readNonEmptyString(record.value, 'grantId', tool);
1875
+ if (!grantId.ok)
1876
+ return grantId;
1877
+ const reason = readNonEmptyString(record.value, 'reason', tool);
1878
+ if (!reason.ok)
1879
+ return reason;
1880
+ const actor = readTaskGrantActor(record.value.actor, tool, ['human', 'system']);
1881
+ if (!actor.ok)
1882
+ return actor;
1883
+ return { ok: true, value: { grantId: grantId.value, actor: actor.value, reason: reason.value } };
1884
+ }
1885
+ function validateRunTaskGrantPreviewMatchInput(input, tool) {
1886
+ const record = inputRecord(input, tool, ['runId', 'agentId', 'taskFingerprint', 'category', 'operation', 'workspaceRoot', 'cwd', 'targetPaths']);
1887
+ if (!record.ok)
1888
+ return record;
1889
+ const runId = readNonEmptyString(record.value, 'runId', tool);
1890
+ if (!runId.ok)
1891
+ return runId;
1892
+ const taskFingerprint = readNonEmptyString(record.value, 'taskFingerprint', tool);
1893
+ if (!taskFingerprint.ok)
1894
+ return taskFingerprint;
1895
+ const category = readRequiredOneOf(record.value, 'category', permissionCategories, tool);
1896
+ if (!category.ok)
1897
+ return category;
1898
+ const operation = readNonEmptyString(record.value, 'operation', tool);
1899
+ if (!operation.ok)
1900
+ return operation;
1901
+ const cwd = readNonEmptyString(record.value, 'cwd', tool);
1902
+ if (!cwd.ok)
1903
+ return cwd;
1904
+ const result = { runId: runId.value, taskFingerprint: taskFingerprint.value, category: category.value, operation: operation.value, cwd: cwd.value };
1905
+ const copied = copyOptionalStrings(result, record.value, tool, ['agentId', 'workspaceRoot']);
1906
+ if (!copied.ok)
1907
+ return copied;
1908
+ const targetPaths = readOptionalStringArray(record.value, 'targetPaths', tool);
1909
+ if (!targetPaths.ok)
1910
+ return targetPaths;
1911
+ if (targetPaths.value !== undefined)
1912
+ result.targetPaths = targetPaths.value;
1913
+ return { ok: true, value: result };
1914
+ }
1620
1915
  function validateRunStartInput(input, tool) {
1621
1916
  const record = inputRecord(input, tool, ['project', 'userIntent', 'workflow', 'phase', 'selectedAgentId', 'providerAdapter', 'model']);
1622
1917
  if (!record.ok)
@@ -1905,6 +2200,68 @@ function readRequiredOneOf(record, field, values, tool) {
1905
2200
  return validationFailure(`${field} must be one of: ${values.join(', ')}`, tool);
1906
2201
  return { ok: true, value: value.value };
1907
2202
  }
2203
+ function readRequiredOneOfArray(record, field, values, tool) {
2204
+ const array = readRequiredStringArray(record, field, tool);
2205
+ if (!array.ok)
2206
+ return array;
2207
+ for (const item of array.value) {
2208
+ if (!values.includes(item))
2209
+ return validationFailure(`${field} must contain only: ${values.join(', ')}`, tool);
2210
+ }
2211
+ return { ok: true, value: array.value };
2212
+ }
2213
+ function readRequiredStringArray(record, field, tool) {
2214
+ if (record[field] === undefined)
2215
+ return validationFailure(`${field} is required`, tool);
2216
+ const values = readOptionalStringArray(record, field, tool);
2217
+ if (!values.ok)
2218
+ return values;
2219
+ if (values.value === undefined || values.value.length === 0)
2220
+ return validationFailure(`${field} must contain at least one item`, tool);
2221
+ if (new Set(values.value).size !== values.value.length)
2222
+ return validationFailure(`${field} must be unique`, tool);
2223
+ return { ok: true, value: values.value };
2224
+ }
2225
+ function readTaskGrantCommandPolicy(value, tool) {
2226
+ const record = asRecord(value, tool, 'commandPolicy must be an object');
2227
+ if (!record.ok)
2228
+ return record;
2229
+ const unexpected = Object.keys(record.value).find((key) => !['kind', 'ids'].includes(key));
2230
+ if (unexpected !== undefined)
2231
+ return validationFailure(`Unexpected commandPolicy field: ${unexpected}`, tool);
2232
+ const kind = readRequiredOneOf(record.value, 'kind', ['allowlisted-command-ids'], tool);
2233
+ if (!kind.ok)
2234
+ return kind;
2235
+ const ids = readRequiredStringArray(record.value, 'ids', tool);
2236
+ if (!ids.ok)
2237
+ return ids;
2238
+ const known = new Set(commandAllowlistIds());
2239
+ for (const id of ids.value)
2240
+ if (!known.has(id))
2241
+ return validationFailure(`Unknown allowlisted command id: ${id}`, tool);
2242
+ return { ok: true, value: { kind: kind.value, ids: ids.value } };
2243
+ }
2244
+ function readTaskGrantActor(value, tool, allowedTypes) {
2245
+ const record = asRecord(value, tool, 'actor must be an object');
2246
+ if (!record.ok)
2247
+ return record;
2248
+ const unexpected = Object.keys(record.value).find((key) => !['type', 'id', 'displayName'].includes(key));
2249
+ if (unexpected !== undefined)
2250
+ return validationFailure(`Unexpected actor field: ${unexpected}`, tool);
2251
+ const type = readRequiredOneOf(record.value, 'type', allowedTypes, tool);
2252
+ if (!type.ok)
2253
+ return type;
2254
+ const id = readNonEmptyString(record.value, 'id', tool);
2255
+ if (!id.ok)
2256
+ return id;
2257
+ const actor = { type: type.value, id: id.value };
2258
+ const displayName = readOptionalNonEmptyString(record.value, 'displayName', tool);
2259
+ if (!displayName.ok)
2260
+ return displayName;
2261
+ if (displayName.value !== undefined)
2262
+ actor.displayName = displayName.value;
2263
+ return { ok: true, value: actor };
2264
+ }
1908
2265
  function readOptionalOneOf(record, field, values, tool) {
1909
2266
  if (record[field] === undefined)
1910
2267
  return { ok: true, value: undefined };
@@ -0,0 +1,37 @@
1
+ CREATE TABLE IF NOT EXISTS task_scoped_command_grants (
2
+ id TEXT PRIMARY KEY,
3
+ run_id TEXT NOT NULL,
4
+ agent_id TEXT NOT NULL,
5
+ task_fingerprint TEXT NOT NULL,
6
+
7
+ categories_json TEXT NOT NULL,
8
+ external_roots_json TEXT NOT NULL,
9
+ canonical_external_roots_json TEXT NOT NULL,
10
+ cwd_patterns_json TEXT,
11
+ command_policy_json TEXT NOT NULL,
12
+
13
+ expires_at TEXT NOT NULL,
14
+ max_uses INTEGER NOT NULL,
15
+ used_count INTEGER NOT NULL DEFAULT 0,
16
+ status TEXT NOT NULL,
17
+
18
+ created_by_json TEXT NOT NULL,
19
+ created_at TEXT NOT NULL,
20
+
21
+ revoked_by_json TEXT,
22
+ revoked_at TEXT,
23
+ revoke_reason TEXT,
24
+
25
+ audit_json TEXT NOT NULL,
26
+
27
+ FOREIGN KEY(run_id) REFERENCES runs(id)
28
+ );
29
+
30
+ CREATE INDEX IF NOT EXISTS idx_task_scoped_command_grants_run
31
+ ON task_scoped_command_grants(run_id);
32
+
33
+ CREATE INDEX IF NOT EXISTS idx_task_scoped_command_grants_match
34
+ ON task_scoped_command_grants(run_id, agent_id, task_fingerprint, status);
35
+
36
+ CREATE INDEX IF NOT EXISTS idx_task_scoped_command_grants_status
37
+ ON task_scoped_command_grants(status);