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.
@@ -160,13 +160,13 @@ export const canonicalOpenCodeManagerPrompt = `# VGXNESS Manager
160
160
  Bind to \`vgxness-manager\` only.
161
161
 
162
162
  ## Role
163
- Coordinate SDD; keep chat thin, use MCP state, delegate to exact SDD subagent. Coach while coordinating: teach briefly; explain practical tradeoffs, risk/effort/unknowns; challenge weak assumptions; keep user in control; avoid lectures.
163
+ Coordinate SDD; keep chat thin, use MCP state, delegate to exact SDD subagent. Coach while coordinating: teach briefly; explain practical tradeoffs, risk/effort/unknowns; challenge weak assumptions; keep user in control; avoid lectures. Always verify before agreeing with technical claims. When blocked, ask one focused question and stop.
164
164
 
165
165
  ## Non-negotiable governance
166
166
  - VGXNESS uses SQLite artifacts/control-plane; OpenCode as primary provider; artifacts are not openspec files. Do not write to \`openspec/\`.
167
167
  - SDD artifact acceptance is human-only. Artifact presence is not acceptance. Never infer or fabricate acceptance from generated output, subagent/model output, file presence, confidence, or legacy artifacts; draft/rejected/superseded/stale/unaccepted artifacts are not accepted until a human acceptance record exists.
168
168
  - Before phase advancement, call \`vgxness_sdd_status\`/\`vgxness_sdd_next\` plus \`vgxness_sdd_ready\` or \`vgxness_sdd_get_readiness\`.
169
- - Before risky VGX-managed side effects (edit, shell/tests, git, network, provider-tool, secrets, external-directory, destructive, privileged, ambiguous), call \`vgxness_run_preflight\` with runId/workflow/phase/agent when available. Stop on approval/block; never invent approval.
169
+ - Before risky VGX-managed side effects (edit, shell/tests, git, network, provider-tool, secrets, external-directory, destructive, privileged, ambiguous), call \`vgxness_run_preflight\` with runId/workflow/phase/agent when available. When the human explicitly requested the exact risky one-off action, pass structured \`explicitRequest\` (human actor, timestamp/source, exact category/operation/scope/risk/parser evidence). If preflight allows with \`authorizationMode: "explicit-request"\`, do not ask duplicate confirmation. Stop on approval-needed or blocked; never invent approval.
170
170
  - Direct human acceptance via \`vgxness_sdd_accept_artifact\` is not a generic SDD write: do not preflight solely for it; require exact project/change/phase, \`acceptedBy.type\` \`"human"\`, non-empty \`acceptedBy.id\`, and eligible status/readiness. Shortcut only for acceptance/trusted draft autorun; excludes edits, shell/tests, git, provider config, memory writes, external paths, secrets, destructive/privileged/ambiguous operations.
171
171
  - bounded read/search repo tools may be enabled for inspection; bash, edit, and write remain denied. Diagnostics are config-level evidence, not host runtime proof.
172
172
  - Do not mutate provider/global OpenCode config. Do not publish packages unless explicitly requested. Never revert/overwrite unrelated user work. Preserve \`permission.task\` deny-by-default with only exact known SDD subagents.
@@ -174,23 +174,24 @@ Coordinate SDD; keep chat thin, use MCP state, delegate to exact SDD subagent. C
174
174
 
175
175
  ## Routing
176
176
  Use lightest safe path: T0-2 direct, T3 preflight, T4 SDD.
177
+ Do not invent or invoke generic reviewer subagents; use only exact canonical SDD subagents or MCP guidance.
177
178
 
178
179
  ## Provider-native daily flow
179
180
  SDD happens in OpenCode conversation + VGXNESS MCP/subagents. No terminal SDD phase commands. CLI is an escape hatch for bootstrap, doctor, recovery, setup gaps, or explicit request.
180
181
 
181
182
  ## MCP playbook
182
- - For starting, resuming, or recovering context: use \`vgxness_resume_context\` first for generic resume/continue prompts; pass \`explicitChange\` only when the user gave an exact valid change. \`resume_context\` is read-only/no-trace/no-mutation and is the selector gate. For retrospective/status prompts like "what did we do?" or "qué habíamos hecho", use recent completed run history first (\`vgxness_run_list completed\`) and mention interrupted candidates only as old blockers, not as latest truth. \`vgxness_context_cockpit\` and \`vgxness_session_restore\` are advisory context only after the gate; \`vgxness_session_restore\` is one signal, not truth. Do not infer a change from \`context_cockpit.memoryPreviews\`; context cockpit previews, \`memoryPreviews\`, and SDD summaries are advisory only. For ending, pausing, handing off, or compacting: \`vgxness_session_close\` with id and actor \`manager\`; if no id, do not invent one; summarize.
183
- - Bounded resume/start hard stop: for generic "continue"/"sigamos"/"continuemos"/"resume development" without an explicit user change, call \`vgxness_resume_context\` before any SDD continuation/status. If it returns \`proceed-explicit-change\` or \`proceed-single-active-work\`, then SDD status/continue may use that change. If it returns \`inspect-interrupted-run\`, call \`vgxness_run_resume_inspect\`. If it returns \`ask-change\`, treat it as: If no interrupted run, no exact user-specified change, and no single parsed active-work change, hard stop: do not call \`vgxness_agent_resolve\`; do not call \`vgxness_sdd_status\` or \`vgxness_sdd_continue\`, do not create runs, do not use Glob/Read/docs, do not inspect repo files/native repo tools, do not delegate to SDD subagents, do not start runs, or use \`skill_index\`/\`skill_search\` fallback; ask one question: "¿qué cambio retomamos?".
183
+ - Start/resume/recover: call \`vgxness_resume_context\` first for generic resume/continue; pass \`explicitChange\` only for an exact user change. Use completed run history for retrospective/status prompts; interrupted candidates are old blockers. \`vgxness_context_cockpit\`/\`vgxness_session_restore\` are advisory after the gate. Do not infer a change from \`context_cockpit.memoryPreviews\`. End/pause/handoff/compact with \`vgxness_session_close\` when a session id exists.
184
+ - Bounded resume/start hard stop: for generic "continue"/"sigamos"/"continuemos"/"resume development" without an explicit change, call \`vgxness_resume_context\` before any SDD continuation/status. \`proceed-explicit-change\` or \`proceed-single-active-work\` may continue with that change; \`inspect-interrupted-run\` means inspect it. On \`ask-change\`: do not call \`vgxness_agent_resolve\`; do not call SDD continuation/status, do not create runs, do not inspect repo files/native repo tools, do not use Glob/Read/docs, do not delegate to SDD subagents, do not start runs or use skill fallback; ask one question: "¿qué cambio retomamos?".
184
185
  - Proposal clarity: if product/business clarity is missing, run a proposal question round.
185
- - SDD artifacts: guide with \`vgxness_sdd_next\`/\`vgxness_sdd_cockpit\`; list/read with \`vgxness_sdd_list_artifacts\`/\`vgxness_sdd_get_artifact\` (\`payloadMode: "compact"\`); verbose preferably inside the delegated phase subagent so primary context stays clean. After hidden subagent returns a valid current-phase artifact, save draft immediately with \`vgxness_sdd_save_artifact\`, summarize it, then ask: "Do you accept this artifact and should I continue to the next phase?" Use \`vgxness_sdd_reopen_artifact\` for rejected artifacts only; \`vgxness_sdd_reopen_artifact\` only for rejected artifacts returning to draft, with explicit human actor/audit context.
186
- - Delegated phase outputs: inspect delegated result-envelope completeness before SDD artifact persistence or phase advancement (status, executive_summary, artifacts_examined, skill_resolution, risks, next_recommended); this is prompt guidance only, not runtime parsing/rejection.
186
+ - SDD artifacts: guide with \`vgxness_sdd_next\`/\`vgxness_sdd_cockpit\`; read/list compact (payloadMode: "compact") via \`vgxness_sdd_get_artifact\`/\`vgxness_sdd_list_artifacts\`. After hidden subagent returns a valid current-phase artifact, save draft immediately with \`vgxness_sdd_save_artifact\`, summarize it, then ask: "Do you accept this artifact and should I continue to the next phase?" Use \`vgxness_sdd_reopen_artifact\` for rejected artifacts only, with explicit human actor/audit context.
187
+ - delegated phase subagent outputs: inspect delegated result-envelope completeness before SDD artifact persistence or phase advancement (status, executive_summary, artifacts_examined, skill_resolution, risks, next_recommended); this is prompt guidance only, not runtime parsing/rejection.
187
188
  - Trusted draft autorun: when the exact \`proposal\` artifact is already accepted, run \`spec -> design -> tasks\` without extra human confirmation via subagents; save drafts with \`vgxness_sdd_save_artifact\`. This is draft-only planning, not acceptance or completion. Excludes explore/proposal/apply-progress/verify/archive, accepted overwrites, risky side effects; re-check status/readiness.
188
- - Acceptance/readiness: check exact status/readiness; call \`vgxness_sdd_accept_artifact\` directly with audit context (\`acceptedBy.type: "human"\`, non-empty \`acceptedBy.id\`, runId, agentId); re-check before reporting. Do not preflight solely for exact acceptance. Affirmative continue/sí/yes/dale to the immediate accept+continue prompt is explicit human acceptance; only save/no/revise keeps draft and must not advance. Never silently auto-accept. Use \`vgxness_governance_report\` for readiness/artifact/preflight audit.
189
- - Memory: \`vgxness_memory_search\`/\`vgxness_memory_get\` for prior work/unclear context and duplicate avoidance. Save/update only durable, actionable, scoped, evidence-backed, compact, non-sensitive observations that are future-relevant: discoveries, decisions, bug fixes, config, patterns, preferences, learnings, or manual notes. \`vgxness_memory_save\` may upsert by \`topicKey\`; \`vgxness_memory_update\` requires a known id. Prefer \`vgxness_memory_update\` over duplicate save when you know the id/topic key. Treat lifecycle \`memoryCapture\` as review-first advisory metadata only: no automatic persistence, no durable queue, explicit save/update policy/consent still required. Do not save full SDD artifacts, transcripts, raw logs, hidden reasoning, secrets, credentials, or sensitive user data. Memory is advisory only and never proves SDD acceptance/readiness/verification/approval/authorization. SDD artifacts are canonical for phase deliverables and governance state. OpenCode previews/handoff are read-only and capture-free. \`memory-write\` requires preflight/explicit consent when governed or risky. Choose scope intentionally: \`project\` for repo facts, \`personal\` for reusable user preferences.
189
+ - Acceptance/readiness: check exact status/readiness; call \`vgxness_sdd_accept_artifact\` directly with audit context; re-check before reporting. Do not preflight solely for exact acceptance. Affirmative continue/sí/yes/dale to the immediate accept+continue prompt is explicit human acceptance; only save/no/revise keeps draft and must not advance. Never silently auto-accept. Use \`vgxness_governance_report\` for audit.
190
+ - Memory: search/get for prior work and duplicate avoidance. Save/update only durable, actionable, scoped, evidence-backed, compact, non-sensitive observations. Prefer \`vgxness_memory_update\` over duplicate save when you know the id/topic key. Treat lifecycle \`memoryCapture\` as review-first advisory metadata only: no automatic persistence, no durable queue. Do not save full SDD artifacts, transcripts, raw logs, hidden reasoning, secrets, credentials, or sensitive user data. Memory is advisory only and never proves SDD acceptance/readiness/verification/approval/authorization. SDD artifacts are canonical for phase deliverables and governance state. OpenCode previews/handoff are read-only and capture-free. Choose scope intentionally: \`project\` for repo facts, \`personal\` for reusable user preferences; \`memory-write\` requires preflight/explicit consent when governed or risky.
190
191
  - Active-work memory uses existing memory APIs only: \`active-work/{change}/summary\` (+others). No secrets or hidden reasoning. Active-work memory is advisory only; never proves SDD acceptance/readiness, verification, approval, or authorization.
191
192
  - Agents/skills: Use \`vgxness_skill_payload\` first for contextual skill guidance; preview/context-only, no provider execution or config writes. Use \`vgxness_skill_index\`/\`vgxness_skill_search\` only for explicit diagnostics/catalog, not normal resume fallback. Resolve phase with \`vgxness_agent_resolve\`; if none resolves, report evidence and ask for the smallest decision instead of inventing agent ids. \`vgxness_agent_activate\` and \`vgxness_opencode_manager_payload\` are preview/context-only. \`vgxness_manager_profile_get\` before changes; \`vgxness_manager_profile_set\` needs explicit human authorization.
192
- - Runs/recovery: \`vgxness_run_start\`/\`vgxness_run_list\`/\`vgxness_run_get\`; checkpoint with \`vgxness_run_checkpoint\`, preflight with \`vgxness_run_preflight\`, close with \`vgxness_run_finalize\`. Unknown runId: \`vgxness_run_resume_candidates\`. For interrupted runs, inspect with \`vgxness_run_resume_inspect\`, then call \`vgxness_run_resume_gate\` with approvalId from pendingApprovals/inspect; never pass runId as approvalId. Follow safe \`recommendedActions[]\`.
193
- - Provider diagnostics: \`vgxness_provider_status\`/\`vgxness_provider_doctor\` read-only; report \`providerEvidence.evidenceLevel\` and \`hostToolPresenceVerified\` limits.
193
+ - Runs/recovery: \`vgxness_run_start\`/\`vgxness_run_list\`/\`vgxness_run_get\`; checkpoint with \`vgxness_run_checkpoint\`, preflight with \`vgxness_run_preflight\` (include \`explicitRequest\` only for exact human-requested one-off actions), close with \`vgxness_run_finalize\`. Unknown runId: \`vgxness_run_resume_candidates\`. For interrupted runs, inspect with \`vgxness_run_resume_inspect\`, then call \`vgxness_run_resume_gate\` with approvalId from pendingApprovals/inspect; never pass runId as approvalId. Follow safe \`recommendedActions[]\`. Explicit request never proves SDD acceptance and never authorizes provider/global config mutation unless the human request names that exact config scope.
194
+ - Provider diagnostics: \`vgxness_provider_status\`/\`vgxness_provider_doctor\` read-only; report \`providerEvidence.evidenceLevel\` and \`hostToolPresenceVerified\` limits; use vgxness_provider_status for configured/phase/next questions plus vgxness_provider_doctor for read-only OpenCode MCP/manager health.
194
195
 
195
196
  ## Minimum flows
196
197
  - Proposal/spec/design/tasks: status/next -> readiness -> prereqs -> subagent -> save draft -> summarize -> ask accept+continue; trusted autorun may draft spec/design/tasks after accepted proposal; drafts stay unaccepted until human acceptance.
@@ -210,11 +211,12 @@ Be concise.`;
210
211
  * intentionally distinct from the provider runtime prompt above.
211
212
  */
212
213
  const registryManagerInstructionsV11 = [
213
- 'You are the VGXNESS SDD coordinator, not a monolithic executor. Coach briefly while coordinating: explain useful tradeoffs, be realistic about risks and unknowns, respectfully challenge weak assumptions with better options, keep the user comfortable and in control, and stay concise.',
214
- 'Default to delegation for SDD phase-shaped work, repository exploration beyond one narrow lookup, implementation, verification, incident recovery, or multi-step analysis. Inline only conversational guidance, single-fact lookup, MCP/status/readiness checks, and trivial mechanical edits when safe. When uncertain between inline work and subagent work, delegate to the exact smallest SDD subagent.',
214
+ 'You are the VGXNESS SDD coordinator, not a monolithic executor. Coach briefly while coordinating: explain useful tradeoffs, be realistic about risks and unknowns, respectfully challenge weak assumptions with better options, keep the user comfortable and in control, and stay concise. Always verify before agreeing with technical claims; when blocked, ask one focused question and stop.',
215
+ 'Default to delegation for SDD phase-shaped work, repository exploration beyond one narrow lookup, implementation, verification, incident recovery, or multi-step analysis. Inline only conversational guidance, single-fact lookup, MCP/status/readiness checks, and trivial mechanical edits when safe. When uncertain between inline work and subagent work, delegate to the exact smallest SDD subagent. do not invent or invoke generic reviewer subagents; use exact canonical SDD subagents or MCP guidance only.',
215
216
  'Use VGXNESS MCP as the durable control plane: for generic resume/continue prompts call vgxness_resume_context first, pass explicitChange only when the user gave an exact valid change, and do not infer a change from context_cockpit.memoryPreviews; for retrospective/status prompts such as "what did we do?" or "qué habíamos hecho", inspect recent completed run history first with vgxness_run_list completed and mention interrupted candidates only as old blockers, not as latest truth; vgxness_context_cockpit and vgxness_session_restore are advisory context only after the gate, and context cockpit previews, memoryPreviews, and SDD summaries are advisory only. Close/pause/compact with vgxness_session_close using actor manager plus an actionable summary when a current session id exists.',
216
217
  'For generic continue/sigamos/continuemos/resume development without an explicit user change, call vgxness_resume_context before any SDD status/continuation. If decision is proceed-explicit-change or proceed-single-active-work, then call SDD tools for that change. If inspect-interrupted-run, call vgxness_run_resume_inspect. If ask-change (no interrupted run, no exact user-specified change, and no single parsed active-work change), hard stop: do not call vgxness_agent_resolve; do not call vgxness_sdd_status or vgxness_sdd_continue, do not create runs, do not inspect repo files/native repo tools, do not delegate to SDD subagents, do not start runs, do not use skill_index/skill_search fallback, or invent agent ids; ask one question: "¿qué cambio retomamos?".',
217
218
  'Manager save-then-accept behavior: after a valid hidden phase subagent artifact, save it immediately as a draft with sdd_save_artifact, summarize the saved draft, then ask whether the human accepts it and wants to continue to the next phase. An affirmative continue/sí/yes/dale reply in that immediate context is explicit human acceptance. A reply of only save/no/revise keeps draft and must not advance. Never silently auto-accept.',
219
+ 'Run preflight explicit-request behavior: when a human explicitly requests the exact risky one-off action, pass structured explicitRequest to vgxness_run_preflight with human actor, request source/timestamp, exact requested category/operation/scope, risk flags, and parser exactness. If preflight returns allowed with authorizationMode explicit-request, do not ask duplicate confirmation. If it returns approval-needed, ask/wait; if blocked, stop. Never use explicitRequest to infer SDD acceptance or to mutate provider/global config unless the human request names that exact config scope.',
218
220
  'Check SDD status/next/ready/cockpit, read prerequisites with sdd_get_artifact or sdd_list_artifacts, use public sdd_continue/internal vgxness_sdd_continue first for advisory read-only continuation plans, use sdd_reopen_artifact only for rejected artifacts returning to draft with explicit human actor/audit context, save phase output with sdd_save_artifact only by governance; inspect delegated result-envelope completeness before SDD artifact persistence or phase advancement (status, executive_summary, artifacts_examined, skill_resolution, risks, next_recommended) as prompt guidance only, not runtime parsing/rejection; when proposal is accepted, spec/design/tasks may run sequentially as draft-only autorun without extra confirmation, while acceptance remains human-only. Memory capture policy: save/update only durable, future-relevant, actionable, scoped, evidence-backed, compact, non-sensitive observations; lifecycle memoryCapture candidates are review-first advisory metadata only, not persisted automatically, not a durable candidate queue, and still require explicit save/update policy and consent; prefer update by known id/topicKey over duplicate saves; never save full SDD artifacts, transcripts, raw logs, hidden reasoning, secrets, credentials, or sensitive user data. Memory is advisory only and never proves SDD acceptance, readiness, verification, approval, or authorization. SDD artifacts are canonical for phase deliverables/governance state. OpenCode previews/handoff are read-only and capture-free. Choose project scope for repo-specific facts and personal scope for reusable user preferences; memory-write requires preflight/explicit consent when governed/risky. Resolve exact SDD subagents before substantial phase work, use runs/checkpoints/preflight/finalize for significant implementation or verification, use run_resume_candidates for unknown runId, inspect interrupted runs by runId with run_resume_inspect, then call run_resume_gate with approvalId from pendingApprovals/inspect; never pass runId as approvalId; use vgxness_provider_status for configured/phase/next questions plus vgxness_provider_doctor for read-only OpenCode MCP/manager health.',
219
221
  'When sdd_continue/vgxness_sdd_continue returns recommendedActions, prefer the first safe action with agentCallable true and humanOnly false; use targetTool plus suggestedArgs as the starting point, and stop for requiresHumanConfirmation, requiresProviderWriteConsent, requiresPreflight, ambiguous, destructive, privileged, external, or outside-approval actions. For readiness, use cockpit/status/readiness to distinguish missing, draft, ready, blocked, and accepted; never infer acceptance. For provider evidence, report providerEvidence.evidenceLevel, hostToolPresenceVerified, notes, and limitations; do not claim true host presence unless evidence explicitly verifies it.',
220
222
  'Prefer payloadMode=compact for manager-facing status/context reads, SDD artifact reads/lists, and activation handoffs so the primary context stays clean; request payloadMode=verbose only when full artifact contents, provider payloads, or skill context are actually needed, preferably inside delegated phase subagents.',
@@ -1,4 +1,5 @@
1
1
  import { canonicalAgentManifest, canonicalDefaultAgentName, canonicalOpenCodeManagerReasoningEffort, canonicalOpenCodeManagerPrompt, canonicalPromptContractVersion, canonicalSddSubagentNames, createCanonicalOpenCodeSddSubagentPrompt, createCanonicalOpenCodeSddTaskPermissions, validateCanonicalAgentManifest, } from './canonical-agent-manifest.js';
2
+ import { composeManagerInstructions } from './manager-profile-overlay-service.js';
2
3
  export function canonicalManifestValidationErrors(manifest = canonicalAgentManifest) {
3
4
  const validation = validateCanonicalAgentManifest(manifest);
4
5
  return validation.ok ? [] : validation.errors;
@@ -55,7 +56,7 @@ export function withEffectiveOpenCodeManagerInstructions(projection, instruction
55
56
  defaultAgent: projection.defaultAgent,
56
57
  agents: {
57
58
  ...projection.agents,
58
- [projection.defaultAgent]: { ...manager, prompt: trimmed },
59
+ [projection.defaultAgent]: { ...manager, prompt: composeManagerInstructions(manager.prompt, trimmed) },
59
60
  },
60
61
  };
61
62
  }
@@ -93,7 +94,7 @@ export function withEffectiveManagerInstructions(projection, instructions) {
93
94
  const trimmed = instructions?.trim() ?? '';
94
95
  if (trimmed === '')
95
96
  return projection;
96
- const agents = projection.agents.map((agent) => agent.canonicalName === projection.defaultAgent ? { ...agent, instructions: trimmed } : agent);
97
+ const agents = projection.agents.map((agent) => agent.canonicalName === projection.defaultAgent ? { ...agent, instructions: composeManagerInstructions(agent.instructions, trimmed) } : agent);
97
98
  return { defaultAgent: projection.defaultAgent, agents };
98
99
  }
99
100
  export function projectCanonicalAgentManifestToClaudeProjectMemory(manifest = canonicalAgentManifest) {
@@ -26,12 +26,23 @@ export class ManagerProfileOverlayService {
26
26
  return ok({
27
27
  manager: {
28
28
  ...manager.value,
29
- instructions: { kind: 'inline', value: overlay.value.instructions },
29
+ instructions: { kind: 'inline', value: composeManagerInstructions(manager.value.instructions.value, overlay.value.instructions) },
30
30
  },
31
31
  overlay: overlay.value,
32
32
  });
33
33
  }
34
34
  }
35
+ export function composeManagerInstructions(baseline, overlay) {
36
+ const baselineInstructions = baseline.trim();
37
+ const overlayInstructions = overlay.trim();
38
+ if (baselineInstructions === '')
39
+ return overlayInstructions;
40
+ if (overlayInstructions === '')
41
+ return baselineInstructions;
42
+ if (overlayInstructions.includes(baselineInstructions))
43
+ return overlayInstructions;
44
+ return `${baselineInstructions}\n\n## Manager profile overlay\n${overlayInstructions}`;
45
+ }
35
46
  function ok(value) {
36
47
  return { ok: true, value };
37
48
  }
@@ -45,8 +56,5 @@ export function computeEffectiveManagerInstructions(database, project = 'vgxness
45
56
  const resolved = service.resolveEffectiveManager({ project, scope, managerName });
46
57
  if (!resolved.ok || resolved.value.overlay === undefined)
47
58
  return undefined;
48
- const overlayInstructions = resolved.value.overlay.instructions.trim();
49
- if (overlayInstructions === '')
50
- return undefined;
51
- return overlayInstructions;
59
+ return resolved.value.manager.instructions.value.trim() || undefined;
52
60
  }
File without changes
@@ -241,6 +241,52 @@ function optionalNumberFlag(flags, name) {
241
241
  const parsed = Number(value);
242
242
  return Number.isInteger(parsed) && parsed > 0 ? { ok: true, value: parsed } : validationFailure(`--${name} must be a positive integer`);
243
243
  }
244
+ function requiredPositiveIntegerFlag(flags, name) {
245
+ const value = requiredFlag(flags, name);
246
+ if (!value.ok)
247
+ return value;
248
+ const parsed = Number(value.value);
249
+ return Number.isSafeInteger(parsed) && parsed > 0 ? { ok: true, value: parsed } : validationFailure(`--${name} must be a positive integer`);
250
+ }
251
+ function taskGrantCategoryFlag(flags, name) {
252
+ const value = requiredFlag(flags, name);
253
+ if (!value.ok)
254
+ return value;
255
+ return value.value === 'shell' || value.value === 'test-run' || value.value === 'git'
256
+ ? { ok: true, value: value.value }
257
+ : validationFailure(`--${name} must be shell, test-run, or git`);
258
+ }
259
+ function optionalTaskGrantStatusFlag(flags, name) {
260
+ const value = optionalStringFlag(flags, name);
261
+ if (value === undefined)
262
+ return { ok: true, value: undefined };
263
+ return value === 'active' || value === 'revoked' || value === 'expired' || value === 'exhausted'
264
+ ? { ok: true, value }
265
+ : validationFailure(`--${name} must be active, revoked, expired, or exhausted`);
266
+ }
267
+ function taskGrantExpirationFlag(flags) {
268
+ const expiresAt = optionalStringFlag(flags, 'expires-at');
269
+ const ttl = optionalStringFlag(flags, 'ttl');
270
+ if ((expiresAt === undefined) === (ttl === undefined))
271
+ return validationFailure('Pass exactly one of --expires-at or --ttl');
272
+ if (expiresAt !== undefined) {
273
+ const timestamp = Date.parse(expiresAt);
274
+ if (!Number.isFinite(timestamp) || new Date(timestamp).toISOString() !== expiresAt)
275
+ return validationFailure('--expires-at must be a valid ISO date-time');
276
+ return timestamp > Date.now() ? { ok: true, value: expiresAt } : validationFailure('--expires-at must be in the future');
277
+ }
278
+ if (ttl === undefined)
279
+ return validationFailure('Pass exactly one of --expires-at or --ttl');
280
+ const match = /^(\d+)(ms|s|m|h|d)$/.exec(ttl);
281
+ if (match === null)
282
+ return validationFailure('--ttl must be a positive duration like 30s, 15m, 2h, or 1d');
283
+ const amount = Number(match[1]);
284
+ if (!Number.isSafeInteger(amount) || amount <= 0)
285
+ return validationFailure('--ttl must be a positive duration like 30s, 15m, 2h, or 1d');
286
+ const unit = match[2];
287
+ const multiplier = unit === 'ms' ? 1 : unit === 's' ? 1_000 : unit === 'm' ? 60_000 : unit === 'h' ? 3_600_000 : 86_400_000;
288
+ return { ok: true, value: new Date(Date.now() + amount * multiplier).toISOString() };
289
+ }
244
290
  function optionalRunStatusFlag(flags, name) {
245
291
  const value = optionalStringFlag(flags, name);
246
292
  if (value === undefined)
@@ -338,4 +384,4 @@ function setupInstallModeFlag(flags) {
338
384
  return { ok: true, value: 'mcp-only' };
339
385
  return { ok: true, value: vgxnessSetupDefaults.defaultInstallMode };
340
386
  }
341
- export { acceptedAtFlag, codeApprovalPolicyFlag, codeMemoryPolicyFlag, codeTranscriptModeFlag, codeVerificationModeFlag, csvFlag, databasePathFor, databasePathSelectionFor, finalRunStatusFlag, instructionKindFlag, isRunStatus, isSkillEvaluationResultStatus, isSkillTargetType, jsonFlag, providerUserGlobalOnlyMigrationMessage, opencodeDiagnosticScopeFlag, opencodeInstallScopeFlag, optionalJsonFlag, optionalModeFlag, optionalNumberFlag, optionalRunStatusFlag, optionalScopeFlag, optionalSkillEvaluationResultStatusFlag, optionalSkillImprovementProposalStatusFlag, optionalSkillResolutionUsageFlag, optionalSkillTargetTypeFlag, optionalSkillVersionStatusFlag, optionalStringFlag, optionalTrimmedFlag, parseArgs, permissionCategoryFlag, requiredFlag, requiredTrimmedFlag, retryPolicyFlag, runOutcomeFlag, scopeFlag, setupDatabaseFlags, setupInstallModeFlag, setupProviderFlag, skillEvaluationResultStatusFlag, skillSourceFromFlags, skillSourceKindFlag, skillTargetTypeFlag, skillUsageOutcomeFlag, };
387
+ export { acceptedAtFlag, codeApprovalPolicyFlag, codeMemoryPolicyFlag, codeTranscriptModeFlag, codeVerificationModeFlag, csvFlag, databasePathFor, databasePathSelectionFor, finalRunStatusFlag, instructionKindFlag, isRunStatus, isSkillEvaluationResultStatus, isSkillTargetType, jsonFlag, providerUserGlobalOnlyMigrationMessage, opencodeDiagnosticScopeFlag, opencodeInstallScopeFlag, optionalJsonFlag, optionalModeFlag, optionalNumberFlag, optionalRunStatusFlag, optionalScopeFlag, optionalSkillEvaluationResultStatusFlag, optionalSkillImprovementProposalStatusFlag, optionalSkillResolutionUsageFlag, optionalSkillTargetTypeFlag, optionalSkillVersionStatusFlag, optionalTaskGrantStatusFlag, optionalStringFlag, optionalTrimmedFlag, parseArgs, permissionCategoryFlag, requiredPositiveIntegerFlag, requiredFlag, requiredTrimmedFlag, retryPolicyFlag, runOutcomeFlag, scopeFlag, setupDatabaseFlags, setupInstallModeFlag, setupProviderFlag, skillEvaluationResultStatusFlag, skillSourceFromFlags, skillSourceKindFlag, skillTargetTypeFlag, skillUsageOutcomeFlag, taskGrantCategoryFlag, taskGrantExpirationFlag, };
@@ -2,7 +2,8 @@ import { AgentRegistryService } from '../../agents/agent-registry-service.js';
2
2
  import { evaluatePermission } from '../../permissions/policy-evaluator.js';
3
3
  import { RunService } from '../../runs/run-service.js';
4
4
  import { RunSnapshotExportService } from '../../runs/run-snapshot-export-service.js';
5
- import { finalRunStatusFlag, jsonFlag, optionalRunStatusFlag, optionalStringFlag, permissionCategoryFlag, requiredFlag, retryPolicyFlag, runOutcomeFlag, } from '../cli-flags.js';
5
+ import { grantEvaluationPayload } from '../../runs/task-scoped-command-grant-service.js';
6
+ import { csvFlag, finalRunStatusFlag, jsonFlag, optionalRunStatusFlag, optionalStringFlag, optionalTaskGrantStatusFlag, permissionCategoryFlag, requiredPositiveIntegerFlag, requiredFlag, retryPolicyFlag, runOutcomeFlag, taskGrantCategoryFlag, taskGrantExpirationFlag, } from '../cli-flags.js';
6
7
  import { usageFailure } from '../cli-help.js';
7
8
  import { isJsonObject, jsonResult, resultFailure, writeJsonFile } from '../cli-helpers.js';
8
9
  function listCliApprovals(service, runId) {
@@ -94,8 +95,158 @@ function permissionRequestFromFlags(parsed, database, environment) {
94
95
  }
95
96
  return { ok: true, value: request };
96
97
  }
98
+ function rejectExecutableGrantFlags(parsed) {
99
+ if (parsed.flags.executable !== undefined || parsed.flags.argv !== undefined)
100
+ return { ok: false, error: { code: 'validation_failed', message: 'Task grants accept --command-id only; arbitrary executable/argv inputs are not supported' } };
101
+ return { ok: true, value: undefined };
102
+ }
103
+ function taskGrantExternalRoots(parsed) {
104
+ const roots = [...csvFlag(parsed.flags, 'external-roots')];
105
+ const root = optionalStringFlag(parsed.flags, 'external-root');
106
+ if (root !== undefined)
107
+ roots.unshift(root);
108
+ return roots.length > 0 ? { ok: true, value: roots } : { ok: false, error: { code: 'validation_failed', message: 'Missing required --external-root or --external-roots' } };
109
+ }
110
+ function runTaskGrantCommand(command, parsed, service) {
111
+ const grantService = service.taskScopedCommandGrantService();
112
+ const executableFlags = rejectExecutableGrantFlags(parsed);
113
+ if (!executableFlags.ok)
114
+ return resultFailure(executableFlags);
115
+ if (command === 'task-grant-create') {
116
+ const runId = requiredFlag(parsed.flags, 'run-id');
117
+ const agentId = requiredFlag(parsed.flags, 'agent-id');
118
+ const taskFingerprint = requiredFlag(parsed.flags, 'task-fingerprint');
119
+ const category = taskGrantCategoryFlag(parsed.flags, 'category');
120
+ const commandId = requiredFlag(parsed.flags, 'command-id');
121
+ const maxUses = requiredPositiveIntegerFlag(parsed.flags, 'max-uses');
122
+ const expiresAt = taskGrantExpirationFlag(parsed.flags);
123
+ const createdById = requiredFlag(parsed.flags, 'created-by-id');
124
+ const creationReason = requiredFlag(parsed.flags, 'creation-reason');
125
+ const approvedScopeSummary = requiredFlag(parsed.flags, 'approved-scope-summary');
126
+ const externalRoots = taskGrantExternalRoots(parsed);
127
+ if (!runId.ok)
128
+ return resultFailure(runId);
129
+ if (!agentId.ok)
130
+ return resultFailure(agentId);
131
+ if (!taskFingerprint.ok)
132
+ return resultFailure(taskFingerprint);
133
+ if (!category.ok)
134
+ return resultFailure(category);
135
+ if (!commandId.ok)
136
+ return resultFailure(commandId);
137
+ if (!maxUses.ok)
138
+ return resultFailure(maxUses);
139
+ if (!expiresAt.ok)
140
+ return resultFailure(expiresAt);
141
+ if (!createdById.ok)
142
+ return resultFailure(createdById);
143
+ if (!creationReason.ok)
144
+ return resultFailure(creationReason);
145
+ if (!approvedScopeSummary.ok)
146
+ return resultFailure(approvedScopeSummary);
147
+ if (!externalRoots.ok)
148
+ return resultFailure(externalRoots);
149
+ const createdByName = optionalStringFlag(parsed.flags, 'created-by-name');
150
+ const cwdPatterns = csvFlag(parsed.flags, 'cwd-patterns');
151
+ return jsonResult(grantService.create({
152
+ runId: runId.value,
153
+ agentId: agentId.value,
154
+ taskFingerprint: taskFingerprint.value,
155
+ categories: [category.value],
156
+ externalRoots: externalRoots.value,
157
+ ...(cwdPatterns.length === 0 ? {} : { cwdPatterns }),
158
+ commandPolicy: { kind: 'allowlisted-command-ids', ids: [commandId.value] },
159
+ expiresAt: expiresAt.value,
160
+ maxUses: maxUses.value,
161
+ createdBy: { type: 'human', id: createdById.value, ...(createdByName === undefined ? {} : { displayName: createdByName }) },
162
+ audit: { creationReason: creationReason.value, approvedScopeSummary: approvedScopeSummary.value },
163
+ }));
164
+ }
165
+ if (command === 'task-grant-list') {
166
+ const runId = requiredFlag(parsed.flags, 'run-id');
167
+ const status = optionalTaskGrantStatusFlag(parsed.flags, 'status');
168
+ const limit = parsed.flags.limit === undefined ? { ok: true, value: undefined } : requiredPositiveIntegerFlag(parsed.flags, 'limit');
169
+ if (!runId.ok)
170
+ return resultFailure(runId);
171
+ if (!status.ok)
172
+ return resultFailure(status);
173
+ if (!limit.ok)
174
+ return resultFailure(limit);
175
+ const agentId = optionalStringFlag(parsed.flags, 'agent-id');
176
+ return jsonResult(grantService.list({
177
+ runId: runId.value,
178
+ ...(agentId === undefined ? {} : { agentId }),
179
+ ...(status.value === undefined ? {} : { status: status.value }),
180
+ ...(limit.value === undefined ? {} : { limit: limit.value }),
181
+ }));
182
+ }
183
+ if (command === 'task-grant-get') {
184
+ const grantId = requiredFlag(parsed.flags, 'grant-id');
185
+ return grantId.ok ? jsonResult(grantService.get(grantId.value)) : resultFailure(grantId);
186
+ }
187
+ if (command === 'task-grant-revoke') {
188
+ const grantId = requiredFlag(parsed.flags, 'grant-id');
189
+ const actorId = requiredFlag(parsed.flags, 'actor-id');
190
+ const reason = requiredFlag(parsed.flags, 'reason');
191
+ const actorType = optionalStringFlag(parsed.flags, 'actor-type') ?? 'human';
192
+ if (!grantId.ok)
193
+ return resultFailure(grantId);
194
+ if (!actorId.ok)
195
+ return resultFailure(actorId);
196
+ if (!reason.ok)
197
+ return resultFailure(reason);
198
+ if (actorType !== 'human' && actorType !== 'system')
199
+ return resultFailure({ ok: false, error: { code: 'validation_failed', message: '--actor-type must be human or system' } });
200
+ const actorName = optionalStringFlag(parsed.flags, 'actor-name');
201
+ return jsonResult(grantService.revoke({
202
+ grantId: grantId.value,
203
+ actor: { type: actorType, id: actorId.value, ...(actorName === undefined ? {} : { displayName: actorName }) },
204
+ reason: reason.value,
205
+ }));
206
+ }
207
+ if (command === 'task-grant-preview') {
208
+ const runId = requiredFlag(parsed.flags, 'run-id');
209
+ const agentId = requiredFlag(parsed.flags, 'agent-id');
210
+ const taskFingerprint = requiredFlag(parsed.flags, 'task-fingerprint');
211
+ const category = taskGrantCategoryFlag(parsed.flags, 'category');
212
+ const commandId = requiredFlag(parsed.flags, 'command-id');
213
+ const workspaceRoot = requiredFlag(parsed.flags, 'workspace-root');
214
+ const cwd = requiredFlag(parsed.flags, 'cwd');
215
+ if (!runId.ok)
216
+ return resultFailure(runId);
217
+ if (!agentId.ok)
218
+ return resultFailure(agentId);
219
+ if (!taskFingerprint.ok)
220
+ return resultFailure(taskFingerprint);
221
+ if (!category.ok)
222
+ return resultFailure(category);
223
+ if (!commandId.ok)
224
+ return resultFailure(commandId);
225
+ if (!workspaceRoot.ok)
226
+ return resultFailure(workspaceRoot);
227
+ if (!cwd.ok)
228
+ return resultFailure(cwd);
229
+ const operation = optionalStringFlag(parsed.flags, 'operation') ?? `command-allowlist:${commandId.value}`;
230
+ const targetPaths = csvFlag(parsed.flags, 'target-paths');
231
+ const preview = grantService.previewMatch({
232
+ runId: runId.value,
233
+ agentId: agentId.value,
234
+ taskFingerprint: taskFingerprint.value,
235
+ category: category.value,
236
+ commandId: commandId.value,
237
+ operation,
238
+ workspaceRoot: workspaceRoot.value,
239
+ cwd: cwd.value,
240
+ ...(targetPaths.length === 0 ? {} : { targetPaths }),
241
+ });
242
+ return preview.ok ? jsonResult({ ok: true, value: grantEvaluationPayload(preview.value) }) : resultFailure(preview);
243
+ }
244
+ return usageFailure(`Unknown runs command: ${command}`);
245
+ }
97
246
  export function runRunsCommand(command, parsed, database, environment) {
98
247
  const service = new RunService(database);
248
+ if (command.startsWith('task-grant-'))
249
+ return runTaskGrantCommand(command, parsed, service);
99
250
  if (command === 'timeline' || command === 'debug' || command === 'resume-plan' || command === 'resume-inspect')
100
251
  return runRunInsightCommand(command, parsed, service);
101
252
  if (command === 'create') {
@@ -164,7 +164,12 @@ function validateCommand(area, command) {
164
164
  command === 'resume-gate' ||
165
165
  command === 'approve' ||
166
166
  command === 'reject' ||
167
- command === 'cancel-approval'
167
+ command === 'cancel-approval' ||
168
+ command === 'task-grant-create' ||
169
+ command === 'task-grant-list' ||
170
+ command === 'task-grant-get' ||
171
+ command === 'task-grant-revoke' ||
172
+ command === 'task-grant-preview'
168
173
  ? { ok: true }
169
174
  : { ok: false, message: `Unknown runs command: ${command}` };
170
175
  }
@@ -55,6 +55,8 @@ export function classifyOperationRisk(input) {
55
55
  evidence.push(`providerTool:${input.providerToolName}`);
56
56
  if (input.destructive === true)
57
57
  return classification(3, ['destructive_operation'], 'plan', 'ask', evidence);
58
+ if (input.providerConfigMutation === true || input.globalConfigMutation === true)
59
+ return classification(3, ['provider_config_write'], 'plan', 'ask', evidence);
58
60
  if (input.external === true || input.category === 'network' || input.category === 'external-directory')
59
61
  return classification(3, ['external_operation'], 'plan', 'ask', evidence);
60
62
  if (input.privileged === true)
@@ -157,6 +157,22 @@ export function callVgxTool(call, services) {
157
157
  return toEnvelope(validated.tool, services.runs.getRun(validated.input.id));
158
158
  case 'vgxness_run_preflight':
159
159
  return toEnvelope(validated.tool, services.runs.preflightOperation(validated.input));
160
+ case 'vgxness_run_task_grant_create':
161
+ return createTaskGrantEnvelope(validated.input, services);
162
+ case 'vgxness_run_task_grant_list':
163
+ return listTaskGrantsEnvelope(validated.input, services);
164
+ case 'vgxness_run_task_grant_get':
165
+ if (services.runs.taskScopedCommandGrantService === undefined)
166
+ return errorEnvelope('validation_failed', 'Task-scoped command grant service is not available', validated.tool);
167
+ return toEnvelope(validated.tool, services.runs.taskScopedCommandGrantService().get(validated.input.grantId));
168
+ case 'vgxness_run_task_grant_revoke':
169
+ if (services.runs.taskScopedCommandGrantService === undefined)
170
+ return errorEnvelope('validation_failed', 'Task-scoped command grant service is not available', validated.tool);
171
+ return toEnvelope(validated.tool, services.runs.taskScopedCommandGrantService().revoke(validated.input));
172
+ case 'vgxness_run_task_grant_preview_match':
173
+ if (services.runs.taskScopedCommandGrantService === undefined)
174
+ return errorEnvelope('validation_failed', 'Task-scoped command grant service is not available', validated.tool);
175
+ return toEnvelope(validated.tool, services.runs.taskScopedCommandGrantService().previewMatch(validated.input));
160
176
  case 'vgxness_run_start':
161
177
  return toEnvelope(validated.tool, services.runs.createRun(validated.input));
162
178
  case 'vgxness_run_checkpoint':
@@ -564,6 +580,24 @@ function listRunsEnvelope(input, services) {
564
580
  ? successEnvelope('vgxness_run_list', result.value.slice(0, limit))
565
581
  : errorEnvelope(result.error.code, result.error.message, 'vgxness_run_list');
566
582
  }
583
+ function createTaskGrantEnvelope(input, services) {
584
+ if (services.runs.taskScopedCommandGrantService === undefined)
585
+ return errorEnvelope('validation_failed', 'Task-scoped command grant service is not available', 'vgxness_run_task_grant_create');
586
+ const { creationReason, approvedScopeSummary, ...grantInput } = input;
587
+ return toEnvelope('vgxness_run_task_grant_create', services.runs.taskScopedCommandGrantService().create({
588
+ ...grantInput,
589
+ audit: { creationReason, approvedScopeSummary },
590
+ }));
591
+ }
592
+ function listTaskGrantsEnvelope(input, services) {
593
+ if (services.runs.taskScopedCommandGrantService === undefined)
594
+ return errorEnvelope('validation_failed', 'Task-scoped command grant service is not available', 'vgxness_run_task_grant_list');
595
+ const { limit, ...filters } = input;
596
+ const result = services.runs.taskScopedCommandGrantService().list(filters);
597
+ return result.ok
598
+ ? successEnvelope('vgxness_run_task_grant_list', limit === undefined ? result.value : result.value.slice(0, limit))
599
+ : errorEnvelope(result.error.code, result.error.message, 'vgxness_run_task_grant_list');
600
+ }
567
601
  function runResumeCandidatesEnvelope(input, services) {
568
602
  const limit = input.limit ?? 5;
569
603
  const result = services.runs.listRecentInterruptedRuns({ project: input.project, limit });