vgxness 0.1.0

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.
Files changed (121) hide show
  1. package/LICENSE +9 -0
  2. package/README.md +110 -0
  3. package/dist/agents/agent-activation-service.js +144 -0
  4. package/dist/agents/agent-registry-service.js +46 -0
  5. package/dist/agents/agent-resolver.js +249 -0
  6. package/dist/agents/agent-seed-service.js +146 -0
  7. package/dist/agents/manager-profile-overlay-service.js +34 -0
  8. package/dist/agents/profile-model-routing.js +26 -0
  9. package/dist/agents/renderers/claude-renderer.js +98 -0
  10. package/dist/agents/renderers/index.js +16 -0
  11. package/dist/agents/renderers/json-renderer.js +87 -0
  12. package/dist/agents/renderers/opencode-renderer.js +100 -0
  13. package/dist/agents/renderers/provider-adapter.js +6 -0
  14. package/dist/agents/repositories/agents.js +185 -0
  15. package/dist/agents/repositories/manager-profile-overlays.js +81 -0
  16. package/dist/agents/schema.js +1 -0
  17. package/dist/cli/dashboard-operational-read-models.js +153 -0
  18. package/dist/cli/dashboard-renderer.js +109 -0
  19. package/dist/cli/dashboard-screen-renderers.js +332 -0
  20. package/dist/cli/dashboard-tui-read-model.js +71 -0
  21. package/dist/cli/dashboard-tui-state.js +218 -0
  22. package/dist/cli/dispatcher.js +2880 -0
  23. package/dist/cli/index.js +27 -0
  24. package/dist/cli/interactive-dashboard.js +29 -0
  25. package/dist/cli/mcp-start-path.js +21 -0
  26. package/dist/cli/setup-status-renderer.js +29 -0
  27. package/dist/cli/setup-wizard-read-model.js +56 -0
  28. package/dist/cli/setup-wizard-renderer.js +148 -0
  29. package/dist/cli/setup-wizard-state.js +82 -0
  30. package/dist/cli/tui-render-helpers.js +192 -0
  31. package/dist/export/redaction.js +71 -0
  32. package/dist/harness/tools/agents.js +245 -0
  33. package/dist/harness/tools/memory.js +29 -0
  34. package/dist/mcp/client-install-opencode-contract.js +227 -0
  35. package/dist/mcp/client-install-opencode.js +194 -0
  36. package/dist/mcp/client-setup-preview.js +38 -0
  37. package/dist/mcp/control-plane.js +175 -0
  38. package/dist/mcp/doctor.js +193 -0
  39. package/dist/mcp/index.js +10 -0
  40. package/dist/mcp/opencode-default-agent-config.js +156 -0
  41. package/dist/mcp/opencode-visibility.js +102 -0
  42. package/dist/mcp/schema.js +234 -0
  43. package/dist/mcp/stdio-server.js +56 -0
  44. package/dist/mcp/validation.js +761 -0
  45. package/dist/memory/import/dry-run-planner.js +58 -0
  46. package/dist/memory/import/index.js +3 -0
  47. package/dist/memory/import/observation-writer.js +220 -0
  48. package/dist/memory/import/package.js +178 -0
  49. package/dist/memory/memory-service.js +126 -0
  50. package/dist/memory/repositories/artifacts.js +41 -0
  51. package/dist/memory/repositories/observations.js +133 -0
  52. package/dist/memory/repositories/sessions.js +105 -0
  53. package/dist/memory/repositories/traces.js +58 -0
  54. package/dist/memory/schema.js +1 -0
  55. package/dist/memory/search.js +11 -0
  56. package/dist/memory/sqlite/database.js +97 -0
  57. package/dist/memory/sqlite/migrations/001_initial.sql +128 -0
  58. package/dist/memory/sqlite/migrations/002_observation_revisions.sql +14 -0
  59. package/dist/memory/sqlite/migrations/003_agent_registry.sql +26 -0
  60. package/dist/memory/sqlite/migrations/004_run_runtime.sql +62 -0
  61. package/dist/memory/sqlite/migrations/005_run_approvals.sql +20 -0
  62. package/dist/memory/sqlite/migrations/006_run_operation_attempts.sql +32 -0
  63. package/dist/memory/sqlite/migrations/007_abandoned_operation_attempts.sql +46 -0
  64. package/dist/memory/sqlite/migrations/008_run_execution_plan_events.sql +105 -0
  65. package/dist/memory/sqlite/migrations/009_multiple_operation_attempts.sql +73 -0
  66. package/dist/memory/sqlite/migrations/010_skill_registry.sql +66 -0
  67. package/dist/memory/sqlite/migrations/011_skill_usage_resolution_outcomes.sql +21 -0
  68. package/dist/memory/sqlite/migrations/012_skill_improvement_proposals.sql +37 -0
  69. package/dist/memory/sqlite/migrations/013_skill_evaluation_scenarios.sql +43 -0
  70. package/dist/memory/sqlite/migrations/014_manager_profile_overlays.sql +14 -0
  71. package/dist/memory/storage-paths.js +72 -0
  72. package/dist/orchestrator/natural-language-planner.js +191 -0
  73. package/dist/orchestrator/schema.js +1 -0
  74. package/dist/permissions/index.js +2 -0
  75. package/dist/permissions/policy-evaluator.js +109 -0
  76. package/dist/permissions/schema.js +1 -0
  77. package/dist/providers/opencode/injection-preview.js +134 -0
  78. package/dist/providers/opencode/manager-payload.js +129 -0
  79. package/dist/runs/execution-planning.js +117 -0
  80. package/dist/runs/operation-execution.js +1 -0
  81. package/dist/runs/operation-retry.js +124 -0
  82. package/dist/runs/repositories/runs.js +611 -0
  83. package/dist/runs/run-insights.js +145 -0
  84. package/dist/runs/run-service.js +713 -0
  85. package/dist/runs/run-snapshot-export-service.js +31 -0
  86. package/dist/runs/sandbox-process-execution.js +218 -0
  87. package/dist/runs/sandbox-worktree-planning.js +59 -0
  88. package/dist/runs/schema.js +1 -0
  89. package/dist/sdd/artifact-portability-service.js +118 -0
  90. package/dist/sdd/schema.js +17 -0
  91. package/dist/sdd/sdd-workflow-service.js +217 -0
  92. package/dist/setup/backup-rollback-service.js +76 -0
  93. package/dist/setup/index.js +3 -0
  94. package/dist/setup/providers/antigravity-setup-adapter.js +18 -0
  95. package/dist/setup/providers/claude-setup-adapter.js +30 -0
  96. package/dist/setup/providers/custom-setup-adapter.js +18 -0
  97. package/dist/setup/providers/index.js +6 -0
  98. package/dist/setup/providers/opencode-setup-adapter.js +104 -0
  99. package/dist/setup/providers/provider-setup-adapter.js +15 -0
  100. package/dist/setup/providers/provider-setup-registry.js +11 -0
  101. package/dist/setup/schema.js +1 -0
  102. package/dist/setup/setup-defaults.js +11 -0
  103. package/dist/setup/setup-lifecycle-service.js +175 -0
  104. package/dist/setup/setup-plan.js +105 -0
  105. package/dist/skills/repositories/skill-evaluation-scenarios.js +289 -0
  106. package/dist/skills/repositories/skill-improvement-proposals.js +288 -0
  107. package/dist/skills/repositories/skills.js +430 -0
  108. package/dist/skills/schema.js +1 -0
  109. package/dist/skills/skill-payload.js +94 -0
  110. package/dist/skills/skill-registry-service.js +92 -0
  111. package/dist/skills/skill-resolver.js +191 -0
  112. package/dist/workflows/command-allowlist-adapter.js +70 -0
  113. package/dist/workflows/schema.js +4 -0
  114. package/dist/workflows/workflow-executor.js +345 -0
  115. package/dist/workflows/workflow-registry.js +66 -0
  116. package/docs/architecture.md +698 -0
  117. package/docs/cli.md +741 -0
  118. package/docs/funcionamiento-del-sistema.md +868 -0
  119. package/docs/harness-gap-analysis.md +229 -0
  120. package/docs/prd.md +372 -0
  121. package/package.json +57 -0
@@ -0,0 +1,191 @@
1
+ const stopWords = new Set(['a', 'an', 'the', 'to', 'for', 'of', 'and', 'or', 'with', 'we', 'before', 'new']);
2
+ const signalRules = [
3
+ { signal: 'diagnostic-request', terms: [/\bdiagnos(e|tic|tics)\b/, /\bfail(ing|ure|ed)?\b/, /\bcrash(es|ing)?\b/, /\berrors?\b/, /\btests?\b/, /\bstatus\b/, /\bhealth\b/, /\blogs?\b/, /\bsetup\b/, /\bdoctor\b/] },
4
+ { signal: 'planning-request', terms: [/\bplan\b/, /\bpreview\b/, /\breview\b/, /\bdry[- ]run\b/] },
5
+ { signal: 'answer-only', terms: [/\bexplain\b/, /\bwhat\b/, /\bhow\b/, /\bwhy\b/, /\bdescribe\b/, /\bunderstand\b/, /\binvestigat(e|ion)\b/, /\binspect\b/, /\bread[- ]only\b/] },
6
+ { signal: 'new-capability', terms: [/\bbuild\b/, /\badd\b/, /\bcreate\b/, /\bnew\b/, /\bcapabilit(y|ies)\b/, /\bfeature\b/] },
7
+ { signal: 'architecture-change', terms: [/\barchitecture\b/, /\barchitectural\b/, /\brefactor\b/, /\bboundar(y|ies)\b/] },
8
+ { signal: 'workflow-change', terms: [/\bworkflow\b/, /\borchestrat(e|ion|or)\b/, /\bsdd\b/, /\bphase\b/] },
9
+ { signal: 'persistence-change', terms: [/\bpersist(ent|ence)?\b/, /\bstorage\b/, /\bsqlite\b/, /\bdatabase\b/, /\bmemory\b/, /\bmigration\b/] },
10
+ { signal: 'security-sensitive', terms: [/\bsecurity\b/, /\bauth\b/, /\btoken\b/, /\bsecret\b/, /\bpermission\b/] },
11
+ { signal: 'broad-change', terms: [/\bmulti[- ]file\b/, /\bacross\b/, /\bend[- ]to[- ]end\b/, /\bsystem\b/] },
12
+ { signal: 'execution-request', terms: [/\brun\b/, /\bexecute\b/, /\bapply\b/, /\bstart\b/, /\binstall\b/, /\bpush\b/] },
13
+ { signal: 'provider-execution', terms: [/\bprovider\b/, /\bopencode\b/, /\bclaude\b/, /\bmodel\b/, /\bllm\b/] },
14
+ { signal: 'file-edit-request', terms: [/\bedit\b/, /\bwrite\b/, /\bmodify\b/, /\bpatch\b/] },
15
+ { signal: 'provider-config-write', terms: [/\bconfig\b/, /\.opencode\b/, /\.claude\b/, /\bglobal\b/] },
16
+ { signal: 'run-recording', terms: [/\brun record\b/, /\brecord a run\b/, /\bcheckpoint\b/] },
17
+ { signal: 'destructive', terms: [/\bdelete\b/, /\bremove\b/, /\bdestroy\b/, /\bdrop\b/, /\breset\b/] },
18
+ { signal: 'external', terms: [/\bnetwork\b/, /\bexternal\b/, /\bdownload\b/, /\bapi\b/] },
19
+ { signal: 'privileged', terms: [/\bsudo\b/, /\badmin\b/, /\bprivileged\b/] },
20
+ ];
21
+ export function createNaturalLanguagePlan(input) {
22
+ const normalizedIntent = normalizeText(input.intent);
23
+ const signals = extractSignals(normalizedIntent);
24
+ const ambiguous = isAmbiguous(normalizedIntent);
25
+ if (ambiguous)
26
+ addSignal(signals, 'ambiguous');
27
+ const flow = chooseFlow(signals);
28
+ const workflow = workflowFor(flow);
29
+ const needsClarification = ambiguous;
30
+ const suggestedChangeId = suggestedChangeFor(input, flow);
31
+ const safety = buildSafety(signals);
32
+ const actionInput = {
33
+ project: input.project,
34
+ intent: input.intent,
35
+ ...(suggestedChangeId !== undefined ? { change: suggestedChangeId } : {}),
36
+ ...(input.sdd !== undefined ? { sdd: input.sdd } : {}),
37
+ };
38
+ const previewActions = buildPreviewActions(actionInput, flow, needsClarification);
39
+ return {
40
+ version: 1,
41
+ project: input.project,
42
+ intent: input.intent,
43
+ flow,
44
+ workflow,
45
+ confidence: confidenceFor(flow, signals, needsClarification),
46
+ reason: reasonFor(flow, signals, needsClarification),
47
+ signals,
48
+ needsClarification,
49
+ ...(needsClarification ? { clarifyingQuestion: 'What target should this change inspect or modify, and what outcome do you want?' } : {}),
50
+ ...(suggestedChangeId !== undefined ? { suggestedChangeId } : {}),
51
+ previewActions,
52
+ safety,
53
+ ...(input.sdd !== undefined ? { sdd: input.sdd } : {}),
54
+ };
55
+ }
56
+ function normalizeText(value) {
57
+ return value.toLowerCase().replace(/[^a-z0-9._/ -]+/g, ' ').replace(/\s+/g, ' ').trim();
58
+ }
59
+ function extractSignals(intent) {
60
+ const signals = [];
61
+ for (const rule of signalRules) {
62
+ if (rule.terms.some((term) => term.test(intent)))
63
+ addSignal(signals, rule.signal);
64
+ }
65
+ if (signals.length === 0 || (/\b(change|fix|update)\b/.test(intent) && !signals.some((signal) => sddSignals.has(signal))))
66
+ addSignal(signals, 'small-local-change');
67
+ return signals;
68
+ }
69
+ function addSignal(signals, signal) {
70
+ if (!signals.includes(signal))
71
+ signals.push(signal);
72
+ }
73
+ const sddSignals = new Set(['architecture-change', 'workflow-change', 'persistence-change', 'security-sensitive', 'broad-change']);
74
+ const unsafeSignals = new Set(['execution-request', 'provider-execution', 'file-edit-request', 'provider-config-write', 'run-recording', 'destructive', 'external', 'privileged']);
75
+ const strongSddSignals = new Set(['architecture-change', 'workflow-change', 'persistence-change', 'security-sensitive', 'broad-change']);
76
+ const strongUnsafeSignals = new Set(['execution-request', 'provider-execution', 'provider-config-write', 'run-recording', 'destructive', 'external', 'privileged']);
77
+ function isAmbiguous(intent) {
78
+ const words = intent.split(' ').filter(Boolean);
79
+ if (words.length <= 2)
80
+ return true;
81
+ return /^(fix|update|change|do|handle) (it|this|that)$/.test(intent);
82
+ }
83
+ function chooseFlow(signals) {
84
+ if (signals.includes('answer-only') && !signals.includes('diagnostic-request') && !signals.includes('new-capability') && !signals.includes('planning-request') && !signals.some((signal) => unsafeSignals.has(signal)))
85
+ return 'explore';
86
+ if (signals.some((signal) => strongSddSignals.has(signal) || strongUnsafeSignals.has(signal)))
87
+ return 'sdd';
88
+ if (signals.includes('diagnostic-request'))
89
+ return 'debug';
90
+ if (signals.includes('answer-only') && !signals.includes('new-capability') && !signals.some((signal) => unsafeSignals.has(signal)))
91
+ return 'explore';
92
+ if (signals.includes('planning-request') && !signals.some((signal) => strongSddSignals.has(signal) || strongUnsafeSignals.has(signal)))
93
+ return 'plan';
94
+ if (signals.some((signal) => sddSignals.has(signal)) || signals.some((signal) => unsafeSignals.has(signal)))
95
+ return 'sdd';
96
+ if (signals.includes('planning-request'))
97
+ return 'plan';
98
+ if (signals.includes('new-capability'))
99
+ return 'build';
100
+ if (signals.includes('small-local-change') || signals.includes('file-edit-request'))
101
+ return 'quickfix';
102
+ return 'explore';
103
+ }
104
+ function workflowFor(flow) {
105
+ if (flow === 'direct')
106
+ return 'explore';
107
+ if (flow === 'diagnose')
108
+ return 'debug';
109
+ return flow;
110
+ }
111
+ function confidenceFor(flow, signals, needsClarification) {
112
+ if (needsClarification)
113
+ return 0.48;
114
+ if (flow === 'sdd' && signals.filter((signal) => sddSignals.has(signal) || unsafeSignals.has(signal)).length >= 2)
115
+ return 0.86;
116
+ if (flow === 'debug' || flow === 'diagnose')
117
+ return 0.82;
118
+ if (flow === 'plan')
119
+ return 0.74;
120
+ if (flow === 'build')
121
+ return 0.7;
122
+ if (flow === 'quickfix')
123
+ return 0.72;
124
+ return 0.72;
125
+ }
126
+ function reasonFor(flow, signals, needsClarification) {
127
+ if (needsClarification)
128
+ return 'The request is missing enough target or goal context, so the planner needs clarification before previewing write actions.';
129
+ if (flow === 'debug' || flow === 'diagnose')
130
+ return 'The request asks for inspection or failure/status diagnosis, so the preview stays read-only.';
131
+ if (flow === 'sdd')
132
+ return 'The request includes capability, architecture, workflow, persistence, safety, or execution risk signals, so SDD is the safest preview path.';
133
+ if (flow === 'plan')
134
+ return 'The request asks for planning or review without immediate mutation, so SDD is not required for this preview.';
135
+ if (flow === 'build')
136
+ return 'The request asks for a clear implementation that does not show formal SDD risk signals.';
137
+ if (flow === 'quickfix')
138
+ return 'The request appears to be a small localized fix, so a lightweight quickfix workflow is sufficient.';
139
+ if (signals.includes('answer-only'))
140
+ return 'The request is answer-only or narrow, so SDD is not required.';
141
+ return 'The request appears narrow and low-risk, so SDD is not required.';
142
+ }
143
+ function suggestedChangeFor(input, flow) {
144
+ if (input.change !== undefined && input.change.trim().length > 0)
145
+ return input.change;
146
+ if (flow !== 'sdd')
147
+ return undefined;
148
+ const words = normalizeText(input.intent)
149
+ .split(' ')
150
+ .map((word) => word.replace(/[^a-z0-9-]/g, ''))
151
+ .filter((word) => word.length > 1 && !stopWords.has(word));
152
+ return words.slice(0, 7).join('-') || undefined;
153
+ }
154
+ function buildSafety(signals) {
155
+ const notes = ['Preview only: this preview only classifies intent; no providers are called, no files are edited, no provider config is written, and no run is recorded.'];
156
+ if (signals.includes('execution-request') || signals.includes('provider-execution') || signals.includes('file-edit-request')) {
157
+ notes.push('Execution was requested but refused in this preview flow; continue manually or through an approved SDD apply phase.');
158
+ }
159
+ if (signals.includes('destructive'))
160
+ notes.push('Destructive impact was detected; review and approval are required before any real operation.');
161
+ if (signals.includes('provider-config-write'))
162
+ notes.push('Provider configuration impact was detected; this preview will not write provider config.');
163
+ if (signals.includes('persistence-change'))
164
+ notes.push('Persistent behavior or storage impact was detected; SDD review is recommended.');
165
+ if (signals.includes('external'))
166
+ notes.push('External or network impact was detected; this preview does not perform external calls.');
167
+ if (signals.includes('privileged'))
168
+ notes.push('Privileged impact was detected; this preview does not request elevated access.');
169
+ return { executed: false, callsProvider: false, editsFiles: false, writesProviderConfig: false, recordsRuns: false, notes };
170
+ }
171
+ function buildPreviewActions(input, flow, needsClarification) {
172
+ if (needsClarification)
173
+ return [{ kind: 'clarification', description: 'Ask for the missing target and desired outcome before previewing write actions.' }];
174
+ if (flow === 'debug' || flow === 'diagnose')
175
+ return [{ kind: 'diagnostic-preview', description: 'Preview read-only diagnostic commands such as status, doctor, logs, or SDD next checks.' }];
176
+ if (flow === 'plan')
177
+ return [{ kind: 'manual-plan', description: 'Draft a manual implementation plan without executing providers or editing files.' }];
178
+ if (flow === 'explore' || flow === 'direct')
179
+ return [{ kind: 'answer', description: 'Explore or answer directly without entering SDD.' }];
180
+ if (flow === 'quickfix')
181
+ return [{ kind: 'workflow-preview', description: 'Preview a small localized quickfix; no execution occurs in this planner response.' }];
182
+ if (flow === 'build')
183
+ return [{ kind: 'workflow-preview', description: 'Preview a scoped build workflow; no execution occurs in this planner response.' }];
184
+ const change = input.change;
185
+ const command = change === undefined ? undefined : `npm run cli -- sdd next --project ${input.project} --change ${change}`;
186
+ return [{
187
+ kind: 'sdd-preview',
188
+ description: input.sdd?.next?.recommendedAction ?? 'Preview the SDD handoff for this substantial or risky request; do not execute it here.',
189
+ ...(command !== undefined ? { command } : {}),
190
+ }];
191
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,2 @@
1
+ export * from './schema.js';
2
+ export * from './policy-evaluator.js';
@@ -0,0 +1,109 @@
1
+ import { existsSync, realpathSync } from 'node:fs';
2
+ import { dirname, isAbsolute, resolve, sep } from 'node:path';
3
+ export const permissionCategories = ['read', 'edit', 'shell', 'network', 'git', 'memory', 'external-directory', 'provider-tool', 'secrets'];
4
+ export const permissionDecisions = ['allow', 'ask', 'deny'];
5
+ export const defaultPermissionPolicy = {
6
+ rules: [
7
+ { category: 'read', decision: 'allow', reason: 'Read-only workspace operations are allowed by default.' },
8
+ { category: 'edit', decision: 'ask', reason: 'File mutation requires approval until enforcement is deeper.' },
9
+ { category: 'shell', decision: 'ask', reason: 'Shell execution can escape intent and requires approval by default.' },
10
+ { category: 'network', decision: 'ask', reason: 'Network access is external and requires approval by default.' },
11
+ { category: 'git', decision: 'ask', reason: 'Git operations can affect review state or remotes.' },
12
+ { category: 'memory', decision: 'ask', reason: 'Memory changes are durable and should be explicit.' },
13
+ { category: 'external-directory', decision: 'deny', reason: 'External directory access is denied unless a future sandbox grants it explicitly.' },
14
+ { category: 'provider-tool', decision: 'ask', reason: 'Provider-specific tools stay opaque and require approval by default.' },
15
+ { category: 'secrets', decision: 'deny', reason: 'Secret access is privileged and denied by default.' },
16
+ ],
17
+ };
18
+ const filesystemCategories = new Set(['read', 'edit', 'external-directory']);
19
+ export function evaluatePermission(request, policy = defaultPermissionPolicy) {
20
+ if (request.category === 'secrets')
21
+ return result(request, 'deny', 'secret_access', 'Secret access is denied by default.');
22
+ const boundary = workspaceBoundaryDecision(request);
23
+ if (boundary !== undefined)
24
+ return boundary;
25
+ const agentDecision = request.agent?.permissions[request.category];
26
+ const baseRule = policy.rules.find((rule) => rule.category === request.category);
27
+ const baseDecision = agentDecision ?? baseRule?.decision ?? 'ask';
28
+ const baseReason = agentDecision !== undefined ? 'agent_policy' : 'default_policy';
29
+ const baseMessage = agentDecision !== undefined
30
+ ? `${request.agent?.mode ?? 'agent'} ${request.agent?.name ?? request.agent?.id ?? 'unknown'} sets ${request.category} to ${agentDecision}.`
31
+ : baseRule?.reason ?? `No explicit rule exists for ${request.category}; ask by default.`;
32
+ const risk = riskDecision(request);
33
+ if (risk !== undefined && isLessRestrictive(baseDecision, risk.decision)) {
34
+ return result(request, risk.decision, risk.reason, risk.message);
35
+ }
36
+ return result(request, baseDecision, baseReason, baseMessage);
37
+ }
38
+ export function isPathInsideWorkspace(workspaceRoot, targetPath) {
39
+ const root = resolve(workspaceRoot);
40
+ const target = resolve(root, targetPath);
41
+ return target === root || target.startsWith(`${root}${sep}`);
42
+ }
43
+ export function resolveWorkspaceContainedPath(workspaceRoot, targetPath) {
44
+ const root = canonicalExistingPath(workspaceRoot);
45
+ if (root === undefined)
46
+ return { ok: false, reason: `Workspace root does not exist: ${workspaceRoot}` };
47
+ const requested = isAbsolute(targetPath) ? resolve(targetPath) : resolve(root, targetPath);
48
+ const parent = canonicalExistingPath(existingAncestor(requested));
49
+ if (parent === undefined)
50
+ return { ok: false, reason: `Target parent cannot be resolved: ${targetPath}` };
51
+ const canonicalTarget = existsSync(requested) ? realpathSync.native(requested) : resolve(parent, requested.slice(existingAncestor(requested).length + 1));
52
+ if (canonicalTarget !== root && !canonicalTarget.startsWith(`${root}${sep}`)) {
53
+ return { ok: false, reason: `Path escapes workspace boundary: ${targetPath}` };
54
+ }
55
+ return { ok: true, workspaceRoot: root, targetPath: canonicalTarget };
56
+ }
57
+ function existingAncestor(targetPath) {
58
+ let current = targetPath;
59
+ while (!existsSync(current)) {
60
+ const parent = dirname(current);
61
+ if (parent === current)
62
+ return current;
63
+ current = parent;
64
+ }
65
+ return current;
66
+ }
67
+ function canonicalExistingPath(targetPath) {
68
+ if (!existsSync(targetPath))
69
+ return undefined;
70
+ return realpathSync.native(targetPath);
71
+ }
72
+ function workspaceBoundaryDecision(request) {
73
+ if (!filesystemCategories.has(request.category))
74
+ return undefined;
75
+ if (request.category === 'external-directory') {
76
+ return result(request, 'deny', 'workspace_boundary', 'External directory access is denied by the foundation policy.');
77
+ }
78
+ if (request.workspaceRoot === undefined || request.targetPath === undefined) {
79
+ return result(request, 'ask', 'ambiguous_operation', 'Filesystem operation needs workspaceRoot and targetPath before it can be safely decided.');
80
+ }
81
+ if (!isPathInsideWorkspace(request.workspaceRoot, request.targetPath)) {
82
+ return result(request, 'deny', 'workspace_boundary', `Path escapes workspace boundary: ${request.targetPath}`);
83
+ }
84
+ return undefined;
85
+ }
86
+ function riskDecision(request) {
87
+ if (request.destructive === true)
88
+ return { decision: 'ask', reason: 'destructive_operation', message: 'Destructive operations require human approval.' };
89
+ if (request.external === true)
90
+ return { decision: 'ask', reason: 'external_operation', message: 'External operations require human approval.' };
91
+ if (request.privileged === true)
92
+ return { decision: 'ask', reason: 'privileged_operation', message: 'Privileged operations require human approval.' };
93
+ if (request.ambiguous === true)
94
+ return { decision: 'ask', reason: 'ambiguous_operation', message: 'Ambiguous operations require human approval.' };
95
+ return undefined;
96
+ }
97
+ function isLessRestrictive(candidate, floor) {
98
+ return rank(candidate) < rank(floor);
99
+ }
100
+ function rank(decision) {
101
+ if (decision === 'allow')
102
+ return 0;
103
+ if (decision === 'ask')
104
+ return 1;
105
+ return 2;
106
+ }
107
+ function result(request, decision, reason, message) {
108
+ return { decision, category: request.category, operation: request.operation, reason, message };
109
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,134 @@
1
+ import { getProviderRenderer } from '../../agents/renderers/index.js';
2
+ const opencodeProvider = 'opencode';
3
+ export class OpenCodeInjectionPreviewService {
4
+ dependencies;
5
+ constructor(dependencies) {
6
+ this.dependencies = dependencies;
7
+ }
8
+ preview(input) {
9
+ const selectors = validateInput(input);
10
+ if (!selectors.ok)
11
+ return selectors;
12
+ const sddStatus = this.dependencies.sdd.getStatus({ project: selectors.value.project, change: selectors.value.change });
13
+ if (!sddStatus.ok)
14
+ return sddStatus;
15
+ const readiness = this.dependencies.sdd.getReady({ project: selectors.value.project, change: selectors.value.change, phase: selectors.value.phase });
16
+ if (!readiness.ok)
17
+ return readiness;
18
+ const agent = this.resolveAgent(selectors.value);
19
+ if (!agent.ok)
20
+ return agent;
21
+ const subagents = this.resolveSubagents(agent.value.id);
22
+ if (!subagents.ok)
23
+ return subagents;
24
+ const renderer = getProviderRenderer(opencodeProvider);
25
+ if (!renderer.ok)
26
+ return renderer;
27
+ const rendered = renderer.value.render({ agent: agent.value, subagents: subagents.value });
28
+ if (!rendered.ok)
29
+ return rendered;
30
+ const payloadOptions = input.workspaceRoot === undefined ? {} : { workspaceRoot: input.workspaceRoot };
31
+ const skillPayload = this.dependencies.skills.buildSkillPayload({
32
+ project: selectors.value.project,
33
+ scope: agent.value.scope,
34
+ agentId: agent.value.id,
35
+ workflow: 'sdd',
36
+ phase: selectors.value.phase,
37
+ providerAdapter: opencodeProvider,
38
+ }, payloadOptions);
39
+ if (!skillPayload.ok)
40
+ return skillPayload;
41
+ return ok({
42
+ version: 1,
43
+ provider: opencodeProvider,
44
+ installable: false,
45
+ readOnly: true,
46
+ context: {
47
+ project: selectors.value.project,
48
+ change: selectors.value.change,
49
+ workflow: 'sdd',
50
+ phase: selectors.value.phase,
51
+ provider: opencodeProvider,
52
+ agent: { id: agent.value.id, name: agent.value.name, scope: agent.value.scope, mode: agent.value.mode },
53
+ },
54
+ providerArtifacts: rendered.value,
55
+ skillPayload: skillPayload.value,
56
+ sdd: { status: sddStatus.value, readiness: readiness.value },
57
+ safety: {
58
+ installable: false,
59
+ readOnly: true,
60
+ executesProvider: false,
61
+ writesProviderConfig: false,
62
+ recordsRuns: false,
63
+ recordsSkillUsage: false,
64
+ },
65
+ warnings: [
66
+ 'OpenCode injection preview is preview-only and installable=false.',
67
+ 'This preview does not execute OpenCode, install hooks, create MCP servers, or create runs.',
68
+ 'This preview does not write provider config, .opencode/, .claude/, or user/global provider files.',
69
+ ...rendered.value.warnings,
70
+ ...skillPayload.value.warnings,
71
+ ],
72
+ });
73
+ }
74
+ resolveAgent(input) {
75
+ if (input.agentId === undefined && input.agentName === 'vgxness-manager' && this.dependencies.managerProfiles !== undefined) {
76
+ const effective = this.dependencies.managerProfiles.resolveEffectiveManager({ project: input.project, scope: input.scope, managerName: input.agentName });
77
+ return effective.ok ? ok(effective.value.manager) : effective;
78
+ }
79
+ const resolved = input.agentId !== undefined
80
+ ? this.dependencies.agents.getAgent(input.agentId)
81
+ : this.dependencies.agents.getAgentByName(input.project, input.scope, input.agentName);
82
+ if (!resolved.ok && resolved.error.code === 'not_found')
83
+ return validationFailure(resolved.error.message);
84
+ return resolved;
85
+ }
86
+ resolveSubagents(agentId) {
87
+ const summaries = this.dependencies.agents.listSubagents(agentId);
88
+ if (!summaries.ok)
89
+ return summaries;
90
+ const subagents = [];
91
+ for (const summary of summaries.value) {
92
+ const subagent = this.dependencies.agents.getAgent(summary.id);
93
+ if (!subagent.ok)
94
+ return subagent;
95
+ subagents.push(subagent.value);
96
+ }
97
+ return ok(subagents);
98
+ }
99
+ }
100
+ function validateInput(input) {
101
+ if (input.provider === undefined || input.provider.trim().length === 0)
102
+ return validationFailure('Provider is required');
103
+ const provider = input.provider.trim().toLowerCase();
104
+ if (provider !== opencodeProvider)
105
+ return validationFailure(`Unsupported provider: ${input.provider}`);
106
+ if (input.project === undefined || input.project.trim().length === 0)
107
+ return validationFailure('Project is required');
108
+ if (input.change === undefined || input.change.trim().length === 0)
109
+ return validationFailure('Change is required');
110
+ if (input.phase === undefined || input.phase.trim().length === 0)
111
+ return validationFailure('Phase is required');
112
+ const agentId = trimOptional(input.agentId);
113
+ const agentName = trimOptional(input.agentName);
114
+ if ((agentId === undefined) === (agentName === undefined)) {
115
+ return validationFailure('Provide exactly one of agentId or agentName');
116
+ }
117
+ const validated = {
118
+ provider: opencodeProvider,
119
+ project: input.project.trim(),
120
+ change: input.change.trim(),
121
+ phase: input.phase.trim(),
122
+ scope: input.scope ?? 'project',
123
+ agentName: agentName ?? '',
124
+ };
125
+ if (agentId !== undefined)
126
+ validated.agentId = agentId;
127
+ return ok(validated);
128
+ }
129
+ function trimOptional(value) {
130
+ const trimmed = value?.trim();
131
+ return trimmed === undefined || trimmed.length === 0 ? undefined : trimmed;
132
+ }
133
+ function ok(value) { return { ok: true, value }; }
134
+ function validationFailure(message) { return { ok: false, error: { code: 'validation_failed', message } }; }
@@ -0,0 +1,129 @@
1
+ import { getProviderRenderer } from '../../agents/renderers/index.js';
2
+ const opencodeProvider = 'opencode';
3
+ const defaultManagerAgentName = 'vgxness-manager';
4
+ export class OpenCodeManagerPayloadService {
5
+ dependencies;
6
+ constructor(dependencies) {
7
+ this.dependencies = dependencies;
8
+ }
9
+ build(input) {
10
+ const validated = validateInput(input);
11
+ if (!validated.ok)
12
+ return validated;
13
+ const agent = this.resolveAgent(validated.value);
14
+ if (!agent.ok)
15
+ return agent;
16
+ if (agent.value.mode !== 'agent')
17
+ return validationFailure('OpenCode manager payload requires a top-level agent');
18
+ const subagents = this.resolveSubagents(agent.value.id);
19
+ if (!subagents.ok)
20
+ return subagents;
21
+ const renderer = getProviderRenderer(opencodeProvider);
22
+ if (!renderer.ok)
23
+ return renderer;
24
+ const rendered = renderer.value.render({ agent: agent.value, subagents: subagents.value });
25
+ if (!rendered.ok)
26
+ return rendered;
27
+ const skillPayload = this.buildSkillPayload(validated.value, agent.value);
28
+ if (!skillPayload.ok)
29
+ return skillPayload;
30
+ return ok({
31
+ version: 1,
32
+ provider: opencodeProvider,
33
+ installable: false,
34
+ selection: {
35
+ requested: requestedSelector(validated.value),
36
+ agent: { id: agent.value.id, name: agent.value.name, scope: agent.value.scope, mode: 'agent' },
37
+ },
38
+ providerArtifacts: rendered.value,
39
+ ...(skillPayload.value !== undefined ? { skillPayload: skillPayload.value } : {}),
40
+ safety: {
41
+ installable: false,
42
+ writesProviderConfig: false,
43
+ executesProvider: false,
44
+ readOnly: true,
45
+ recordsSkillUsage: false,
46
+ loadsSeeds: false,
47
+ },
48
+ warnings: [
49
+ 'OpenCode manager payload is preview-only and installable=false.',
50
+ 'This payload does not execute OpenCode, load seeds, record skill usage, or write provider configuration.',
51
+ ...rendered.value.warnings,
52
+ ...(skillPayload.value?.warnings ?? []),
53
+ ],
54
+ });
55
+ }
56
+ resolveAgent(input) {
57
+ if (input.agentId !== undefined) {
58
+ const byId = this.dependencies.agents.getAgent(input.agentId);
59
+ if (!byId.ok)
60
+ return byId;
61
+ if (input.agentName !== undefined && byId.value.name !== input.agentName) {
62
+ return validationFailure(`agentId resolves to ${byId.value.name}, not ${input.agentName}`);
63
+ }
64
+ return byId;
65
+ }
66
+ const name = input.agentName ?? defaultManagerAgentName;
67
+ if (input.agentName === undefined && input.useManagerOverlay === true && this.dependencies.managerProfiles !== undefined) {
68
+ const effective = this.dependencies.managerProfiles.resolveEffectiveManager({ project: input.project, scope: input.scope, managerName: name });
69
+ return effective.ok ? ok(effective.value.manager) : effective;
70
+ }
71
+ return this.dependencies.agents.getAgentByName(input.project, input.scope, name);
72
+ }
73
+ resolveSubagents(agentId) {
74
+ const summaries = this.dependencies.agents.listSubagents(agentId);
75
+ if (!summaries.ok)
76
+ return summaries;
77
+ const subagents = [];
78
+ for (const summary of summaries.value) {
79
+ const subagent = this.dependencies.agents.getAgent(summary.id);
80
+ if (!subagent.ok)
81
+ return subagent;
82
+ subagents.push(subagent.value);
83
+ }
84
+ return ok(subagents);
85
+ }
86
+ buildSkillPayload(input, agent) {
87
+ const options = {};
88
+ if (input.workspaceRoot !== undefined)
89
+ options.workspaceRoot = input.workspaceRoot;
90
+ if (input.maxSourceBytes !== undefined)
91
+ options.maxSourceBytes = input.maxSourceBytes;
92
+ const payload = this.dependencies.skills.buildSkillPayload({
93
+ project: input.project,
94
+ scope: agent.scope,
95
+ agentId: agent.id,
96
+ providerAdapter: opencodeProvider,
97
+ }, options);
98
+ return payload.ok ? ok(payload.value) : payload;
99
+ }
100
+ }
101
+ function validateInput(input) {
102
+ if (input.project.trim().length === 0)
103
+ return validationFailure('Project is required');
104
+ if (input.agentId !== undefined && input.agentId.trim().length === 0)
105
+ return validationFailure('agentId must not be empty');
106
+ if (input.agentName !== undefined && input.agentName.trim().length === 0)
107
+ return validationFailure('agentName must not be empty');
108
+ const validated = { project: input.project.trim(), scope: input.scope ?? 'project' };
109
+ if (input.agentId !== undefined)
110
+ validated.agentId = input.agentId.trim();
111
+ if (input.agentName !== undefined)
112
+ validated.agentName = input.agentName.trim();
113
+ if (input.useManagerOverlay !== undefined)
114
+ validated.useManagerOverlay = input.useManagerOverlay;
115
+ if (input.workspaceRoot !== undefined)
116
+ validated.workspaceRoot = input.workspaceRoot;
117
+ if (input.maxSourceBytes !== undefined)
118
+ validated.maxSourceBytes = input.maxSourceBytes;
119
+ return ok(validated);
120
+ }
121
+ function requestedSelector(input) {
122
+ if (input.agentId !== undefined)
123
+ return 'agentId';
124
+ if (input.agentName !== undefined && input.agentName !== defaultManagerAgentName)
125
+ return 'agentName';
126
+ return 'default-manager';
127
+ }
128
+ function ok(value) { return { ok: true, value }; }
129
+ function validationFailure(message) { return { ok: false, error: { code: 'validation_failed', message } }; }