vgxness 1.18.0 → 1.19.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.
@@ -0,0 +1,28 @@
1
+ export const GLOBAL_PERSONAL_AGENTS_PROJECT = '__personal__';
2
+ export const GLOBAL_VGXNESS_AGENTS_PROJECT = '__vgxness__';
3
+ export const LEGACY_VGXNESS_AGENTS_PROJECT = 'vgxness';
4
+ export const agentLookupContexts = (project, scope) => {
5
+ const contexts = [{ project, scope }];
6
+ if (scope !== 'personal')
7
+ contexts.push({ project, scope: 'personal' });
8
+ if (project !== GLOBAL_PERSONAL_AGENTS_PROJECT)
9
+ contexts.push({ project: GLOBAL_PERSONAL_AGENTS_PROJECT, scope: 'personal' });
10
+ if (project !== GLOBAL_VGXNESS_AGENTS_PROJECT)
11
+ contexts.push({ project: GLOBAL_VGXNESS_AGENTS_PROJECT, scope: 'project' });
12
+ if (project !== LEGACY_VGXNESS_AGENTS_PROJECT)
13
+ contexts.push({ project: LEGACY_VGXNESS_AGENTS_PROJECT, scope: 'project' });
14
+ return dedupeContexts(contexts);
15
+ };
16
+ export const dedupeAgentLookupContexts = (contexts) => {
17
+ return dedupeContexts(contexts);
18
+ };
19
+ function dedupeContexts(contexts) {
20
+ const seen = new Set();
21
+ return contexts.filter((context) => {
22
+ const key = `${context.project}:${context.scope}`;
23
+ if (seen.has(key))
24
+ return false;
25
+ seen.add(key);
26
+ return true;
27
+ });
28
+ }
@@ -1,3 +1,4 @@
1
+ import { agentLookupContexts } from './agent-lookup-contexts.js';
1
2
  import { AgentResolver } from './agent-resolver.js';
2
3
  import { AgentRepository } from './repositories/agents.js';
3
4
  export class AgentRegistryService {
@@ -32,9 +33,9 @@ export class AgentRegistryService {
32
33
  this.database.close();
33
34
  }
34
35
  listAgentDefinitions(input) {
35
- const filters = {};
36
36
  if (input.project !== undefined)
37
- filters.project = input.project;
37
+ return this.listAgentDefinitionsForContexts(input);
38
+ const filters = {};
38
39
  if (input.scope !== undefined)
39
40
  filters.scope = input.scope;
40
41
  if (input.mode !== undefined)
@@ -51,4 +52,30 @@ export class AgentRegistryService {
51
52
  }
52
53
  return { ok: true, value: definitions };
53
54
  }
55
+ listAgentDefinitionsForContexts(input) {
56
+ const project = input.project;
57
+ if (project === undefined)
58
+ return { ok: true, value: [] };
59
+ const summariesByName = new Map();
60
+ for (const context of agentLookupContexts(project, input.scope ?? 'project')) {
61
+ const filters = { project: context.project, scope: context.scope };
62
+ if (input.mode !== undefined)
63
+ filters.mode = input.mode;
64
+ const summaries = this.agents.list(filters);
65
+ if (!summaries.ok)
66
+ return summaries;
67
+ for (const summary of summaries.value) {
68
+ if (!summariesByName.has(summary.name))
69
+ summariesByName.set(summary.name, summary);
70
+ }
71
+ }
72
+ const definitions = [];
73
+ for (const summary of summariesByName.values()) {
74
+ const agent = this.agents.getById(summary.id);
75
+ if (!agent.ok)
76
+ return agent;
77
+ definitions.push(agent.value);
78
+ }
79
+ return { ok: true, value: definitions };
80
+ }
54
81
  }
@@ -1,4 +1,5 @@
1
1
  import { normalizeSddPhaseInput } from '../sdd/schema.js';
2
+ import { agentLookupContexts } from './agent-lookup-contexts.js';
2
3
  export class AgentResolver {
3
4
  loadAgents;
4
5
  constructor(loadAgents) {
@@ -59,9 +60,13 @@ function normalizeInput(input) {
59
60
  const desiredCapabilities = normalizeList(input.desiredCapabilities ?? []);
60
61
  const taskDescription = normalizeText(input.taskDescription);
61
62
  const intent = normalizeText(input.intent);
62
- const phase = input.phase === undefined ? undefined : (isSddWorkflow(input.workflow) ? (normalizeSddPhaseInput(input.phase) ?? input.phase) : input.phase);
63
+ const metaWorkflow = isMetaWorkflow(input.workflow);
64
+ const workflow = normalizeMetaWorkflow(input.workflow);
65
+ const phase = metaWorkflow || input.phase === undefined ? undefined : (isSddWorkflow(workflow) ? (normalizeSddPhaseInput(input.phase) ?? input.phase) : input.phase);
66
+ const { workflow: _workflow, phase: _phase, ...rest } = input;
63
67
  return {
64
- ...input,
68
+ ...rest,
69
+ ...(workflow !== undefined ? { workflow } : {}),
65
70
  ...(phase !== undefined ? { phase } : {}),
66
71
  ...(taskDescription !== undefined ? { taskDescription } : {}),
67
72
  ...(intent !== undefined ? { intent } : {}),
@@ -69,11 +74,19 @@ function normalizeInput(input) {
69
74
  terms: tokenize([taskDescription, intent].filter((value) => value !== undefined).join(' ')),
70
75
  };
71
76
  }
77
+ function normalizeMetaWorkflow(workflow) {
78
+ if (isMetaWorkflow(workflow))
79
+ return undefined;
80
+ return workflow;
81
+ }
82
+ function isMetaWorkflow(workflow) {
83
+ return workflow?.trim().toLowerCase() === 'workflow-selection';
84
+ }
72
85
  function hardSkipReasons(agent, input) {
73
86
  const reasons = [];
74
- if (input.project !== undefined && agent.project !== input.project)
75
- reasons.push(`project mismatch: ${agent.project}`);
76
- if (input.scope !== undefined && agent.scope !== input.scope)
87
+ if (input.project !== undefined && !agentMatchesRequestedContext(agent, input))
88
+ reasons.push(`context mismatch: ${agent.project}/${agent.scope}`);
89
+ else if (input.project === undefined && input.scope !== undefined && agent.scope !== input.scope)
77
90
  reasons.push(`scope mismatch: ${agent.scope}`);
78
91
  if (input.mode !== undefined && agent.mode !== input.mode)
79
92
  reasons.push(`mode mismatch: ${agent.mode}`);
@@ -85,6 +98,12 @@ function hardSkipReasons(agent, input) {
85
98
  reasons.push('capability mismatch');
86
99
  return reasons;
87
100
  }
101
+ function agentMatchesRequestedContext(agent, input) {
102
+ if (input.project === undefined)
103
+ return true;
104
+ const contexts = agentLookupContexts(input.project, input.scope ?? 'project');
105
+ return contexts.some((context) => agent.project === context.project && agent.scope === context.scope);
106
+ }
88
107
  function scoreAgent(agent, input) {
89
108
  const reasons = [];
90
109
  let score = 0;
@@ -1,20 +1,25 @@
1
+ import { agentLookupContexts } from './agent-lookup-contexts.js';
1
2
  export const agentSelectorNameFallbackCode = 'AGENT_SELECTOR_NAME_FALLBACK';
2
3
  export function resolveAgentSelector(input, lookup) {
3
4
  const byId = lookup.getById(input.selector);
4
5
  if (byId.ok)
5
6
  return ok({ agent: byId.value });
6
- const byName = lookup.getByName(input.project, input.scope, input.selector);
7
- if (byName.ok) {
8
- return ok({
9
- agent: byName.value,
10
- warning: `${agentSelectorNameFallbackCode}: selector "${input.selector}" matched scoped agent name; using canonical agent id "${byName.value.id}" name "${byName.value.name}".`,
11
- });
7
+ for (const context of agentLookupContexts(input.project, input.scope)) {
8
+ const byName = lookup.getByName(context.project, context.scope, input.selector);
9
+ if (byName.ok) {
10
+ return ok({
11
+ agent: byName.value,
12
+ warning: `${agentSelectorNameFallbackCode}: selector "${input.selector}" matched agent name in ${context.project}/${context.scope}; using canonical agent id "${byName.value.id}" name "${byName.value.name}".`,
13
+ });
14
+ }
15
+ if (byName.error.code !== 'not_found')
16
+ return byName;
12
17
  }
13
18
  return {
14
19
  ok: false,
15
20
  error: {
16
21
  code: 'not_found',
17
- message: `Agent selector not found: "${input.selector}". Looked up registry id first, then scoped agent name for project "${input.project}" and scope "${input.scope}". Accepted forms: canonical agent id or agent name within the requested project/scope.`,
22
+ message: `Agent selector not found: "${input.selector}". Looked up registry id first, then agent name across project, personal, and VGXNESS global contexts for project "${input.project}" and scope "${input.scope}". Accepted forms: canonical agent id or visible agent name.`,
18
23
  },
19
24
  };
20
25
  }
@@ -2,7 +2,7 @@ import { canonicalBehaviorContractVersion } from '../behavior/behavior-contract-
2
2
  export const canonicalDefaultAgentName = 'vgxness-manager';
3
3
  export const canonicalOpenCodeDefaultModel = 'openai/gpt-5.5';
4
4
  export const canonicalOpenCodeManagerReasoningEffort = 'high';
5
- export const canonicalPromptContractVersion = 12;
5
+ export const canonicalPromptContractVersion = 15;
6
6
  export const canonicalSddSubagentNames = [
7
7
  'vgxness-sdd-explore',
8
8
  'vgxness-sdd-propose',
@@ -79,7 +79,7 @@ function managerDefinition() {
79
79
  name: canonicalDefaultAgentName,
80
80
  description: 'Coordinates VGXNESS MCP state and SDD sub-agents while routing Tier 0-2 lightweight work, Tier 3 preflight validation, and Tier 4 formal SDD.',
81
81
  instructions: { kind: 'inline', value: registryManagerInstructionsV11 },
82
- capabilities: ['sdd-orchestration', 'agent-routing', 'mcp-coordination', 'project-local-automation'],
82
+ capabilities: ['sdd-orchestration', 'agent-routing', 'mcp-coordination', 'project-local-automation', 'coordination', 'workflow-selection', 'resume'],
83
83
  permissions: { read: 'allow', edit: 'ask', shell: 'ask', git: 'ask', memory: 'allow', 'provider-tool': 'deny', secrets: 'deny' },
84
84
  memory: { scopes: ['project'] },
85
85
  workflows: ['explore', 'quickfix', 'plan', 'build', 'debug', 'sdd', 'agent-seeding', 'opencode-install'],
@@ -165,25 +165,27 @@ Coordinate SDD; keep chat thin, use VGXNESS MCP state, delegate to exact SDD sub
165
165
  - 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.
166
166
  - Before phase advancement, call \`vgxness_sdd_status\`/\`vgxness_sdd_next\` plus \`vgxness_sdd_ready\` or \`vgxness_sdd_get_readiness\`.
167
167
  - 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.
168
- - Direct human acceptance of an exact SDD artifact via \`vgxness_sdd_accept_artifact\` is not a generic SDD write for manager routing: do not call \`vgxness_run_preflight\` solely for that acceptance; require the user explicitly accepted the exact project/change/phase artifact, \`acceptedBy.type\` is \`"human"\`, \`acceptedBy.id\` is non-empty, and status/readiness confirms the artifact is eligible. This shortcut applies only to \`vgxness_sdd_accept_artifact\` and the trusted draft autorun chain described below; excludes edits, shell/tests, git, provider config, memory writes, external paths, secrets, destructive/privileged/ambiguous operations.
169
- - OpenCode native/provider tools are governance-v1 audit-only/non-hard-blocking. Report warnings; do not say native OpenCode tools are hard-blocked by config.
168
+ - 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.
169
+ - manager native repo tools are disabled in config (read/glob/edit/write/bash); report config-level enforcement, not proof of host runtime behavior. Subagents keep phase tools.
170
170
  - 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.
171
171
  - Do not change provider model/reasoning config unless explicitly requested.
172
172
 
173
- ## Flexible governance routing
174
- Use lightest safe path: T0-2 direct, T3 preflight, T4 formal SDD for governance, permissions, acceptance, architecture/security, or cross-surface behavior. Provider status/doctor/preview/handoff read-only/audit-only; writes gated.
173
+ ## Routing
174
+ Use lightest safe path: T0-2 direct, T3 preflight, T4 SDD for governance/permissions/acceptance/architecture/security/cross-surface behavior. Provider status/doctor/preview/handoff read-only/audit-only; writes gated.
175
175
 
176
176
  ## Provider-native daily flow
177
177
  SDD happens in OpenCode via conversation, VGXNESS MCP, and SDD subagents. No terminal SDD phase commands. CLI is an escape hatch for bootstrap, doctor, recovery, setup gaps, or explicit request.
178
178
 
179
179
  ## MCP playbook
180
- - For starting, resuming, or recovering context: prefer \`vgxness_context_cockpit\`; treat \`vgxness_session_restore\` as one signal, not truth. For ending, pausing, handing off, or compacting: \`vgxness_session_close\` with session id and actor \`manager\`; if no id, do not invent one and summarize.
180
+ - For starting, resuming, or recovering context: prefer \`vgxness_context_cockpit\`; \`vgxness_session_restore\` is one signal, not truth. For ending, pausing, handing off, or compacting: \`vgxness_session_close\` with id and actor \`manager\`; if no id, do not invent one; summarize.
181
+ - Bounded resume/start hard stop: for "continue"/"sigamos"/"resume development", call compact \`vgxness_skill_payload\`, \`vgxness_context_cockpit\`, \`vgxness_run_resume_candidates\`, then \`vgxness_agent_resolve\` only if an executor is needed. If no interrupted run or exact SDD change/next action exists, stop: do not use Glob/Read/docs, do not inspect repo files, do not delegate to SDD subagents, do not start runs; return status and ask one question: "¿qué cambio retomamos?". \`skill_index\`/\`skill_search\` are not resume fallback.
181
182
  - Proposal clarity: if product/business clarity is missing, run a proposal question round.
182
183
  - SDD artifacts: guide with \`vgxness_sdd_next\`/\`vgxness_sdd_cockpit\`; list/read with \`vgxness_sdd_get_artifact\`/\`vgxness_sdd_list_artifacts\`; prefer \`payloadMode: "compact"\` so primary context stays clean; use verbose only when required, preferably inside the delegated phase subagent. Save with \`vgxness_sdd_save_artifact\` after the right flow. Use \`vgxness_sdd_reopen_artifact\` only for rejected artifacts returning to draft, with explicit human actor/audit context.
183
- - Trusted draft autorun: when the exact \`proposal\` artifact is already accepted, run exactly \`spec -> design -> tasks\` without extra human confirmation via exact subagents; save drafts with \`vgxness_sdd_save_artifact\`. This is draft-only planning, not acceptance or completion. Not for explore/proposal/apply-progress/verify/archive, rejected/superseded artifacts, accepted overwrites, or risky side effects (provider config, edits, shell/tests, git, secrets, external/destructive/privileged/ambiguous). Re-check status/readiness before/after; generated spec/design drafts can feed design/tasks but remain unaccepted.
184
- - Acceptance/readiness: check SDD status/readiness for the exact project/change/phase; confirm explicit human acceptance; call \`vgxness_sdd_accept_artifact\` directly with audit context (\`acceptedBy.type: "human"\`, non-empty \`acceptedBy.id\`, runId, agentId); re-check status/readiness before reporting state. Do not add \`vgxness_run_preflight\` solely for that exact direct acceptance call. Ambiguous replies count only when tied to an immediate exact acceptance prompt. Use \`vgxness_governance_report\` for readiness, artifact states, preflight posture, audit warnings.
185
- - Memory: \`vgxness_memory_search\`/\`vgxness_memory_get\` for prior work/unclear context; \`vgxness_memory_save\` for durable discoveries, decisions, bug fixes, config, patterns, or preferences; \`vgxness_memory_update\` only to correct/evolve a known id. Do not duplicate full SDD artifacts as memory.
186
- - Agents/skills: Skill registry index-first; resolve skills by registry, exact path, and task context; read exact paths; do not invent summaries. Resolve phase with \`vgxness_agent_resolve\`. Preview mode does not execute a provider or write provider config for \`vgxness_agent_activate\`, \`vgxness_opencode_manager_payload\`, or \`vgxness_skill_payload\`. \`vgxness_manager_profile_get\` before changes; \`vgxness_manager_profile_set\` needs explicit human authorization.
184
+ - 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.
185
+ - Acceptance/readiness: check exact status/readiness; confirm explicit human acceptance; 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. Ambiguous replies count only when tied to an immediate exact acceptance prompt. Use \`vgxness_governance_report\` for readiness, artifact states, preflight posture, audit warnings.
186
+ - Memory: \`vgxness_memory_search\`/\`vgxness_memory_get\` for prior work/unclear context; \`vgxness_memory_save\` for durable discoveries/decisions/bug fixes/config/patterns/preferences; \`vgxness_memory_update\` only for a known id. Do not duplicate full SDD artifacts as memory.
187
+ - 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, or authorization.
188
+ - 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.
187
189
  - 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[]\`.
188
190
  - Provider diagnostics: \`vgxness_provider_status\`/\`vgxness_provider_doctor\` read-only; report \`providerEvidence.evidenceLevel\` and \`hostToolPresenceVerified\` limits.
189
191
 
@@ -209,6 +211,7 @@ const registryManagerInstructionsV11 = [
209
211
  '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.',
210
212
  '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.',
211
213
  'Use VGXNESS MCP as the durable control plane: restore session context with vgxness_session_restore before inferring start/resume state from chat, and close/pause/compact with vgxness_session_close using actor manager plus an actionable summary when a current session id exists.',
214
+ 'For continue/sigamos/resume development, run only bounded resume/start: compact skill_payload, context_cockpit, run_resume_candidates, then agent_resolve only if needed. If no interrupted run or exact SDD change/next action exists, hard stop: do not use Glob/Read/docs, inspect repo files, delegate to SDD subagents, start runs, use skill_index/skill_search fallback, or invent agent ids; summarize status and ask one question: "¿qué cambio retomamos?".',
212
215
  '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; when proposal is accepted, spec/design/tasks may run sequentially as draft-only autorun without extra confirmation, while acceptance remains human-only. Search/get/save/update memory only for reusable knowledge, 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.',
213
216
  '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.',
214
217
  '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.',
@@ -20,10 +20,10 @@ export function projectCanonicalAgentManifestToOpenCode(manifest = canonicalAgen
20
20
  mode: 'primary',
21
21
  ...(manager.adapters?.opencode?.model !== undefined ? { model: manager.adapters.opencode.model } : {}),
22
22
  options: { reasoningEffort: canonicalOpenCodeManagerReasoningEffort, vgxnessPromptContractVersion: canonicalPromptContractVersion },
23
- permission: { ...openCodePermissionsFor(manager, { task: createCanonicalOpenCodeSddTaskPermissions() }), bash: 'allow' },
23
+ permission: openCodeManagerPermissionsFor(manager),
24
24
  prompt: canonicalOpenCodeManagerPrompt,
25
25
  reasoningEffort: canonicalOpenCodeManagerReasoningEffort,
26
- tools: { bash: true, delegate: true, delegation_list: true, delegation_read: true, edit: true, read: true, write: true },
26
+ tools: openCodeManagerTools(),
27
27
  variant: '',
28
28
  },
29
29
  };
@@ -132,6 +132,28 @@ function openCodePermissionsFor(agent, additional = {}) {
132
132
  const adapterPermission = asRecord(agent.adapters?.opencode?.config?.permission);
133
133
  return { ...adapterPermission, ...additional, ...mapCanonicalPermissionsToOpenCode(agent.permissions) };
134
134
  }
135
+ function openCodeManagerPermissionsFor(agent) {
136
+ return {
137
+ ...openCodePermissionsFor(agent, { task: createCanonicalOpenCodeSddTaskPermissions() }),
138
+ bash: 'deny',
139
+ edit: 'deny',
140
+ read: 'deny',
141
+ write: 'deny',
142
+ };
143
+ }
144
+ function openCodeManagerTools() {
145
+ return {
146
+ bash: false,
147
+ delegate: true,
148
+ delegation_list: true,
149
+ delegation_read: true,
150
+ edit: false,
151
+ glob: false,
152
+ grep: false,
153
+ read: false,
154
+ write: false,
155
+ };
156
+ }
135
157
  function mapCanonicalPermissionsToOpenCode(permissions) {
136
158
  const mapped = {};
137
159
  if (permissions === undefined)
@@ -69,7 +69,7 @@ Areas:
69
69
  mcp doctor [--db <path>] [--project <name>] [--change <id>] [--timeout-ms <ms>]
70
70
  MCP setup preview is read-only; it does not install or write .opencode/, .claude/, or provider config.
71
71
  Without --db, MCP install and setup commands use the vgxness global default database; pass --db .vgx/memory.sqlite for project-local compatibility.
72
- OpenCode install defaults to user-global scope and installs mcp.vgxness plus top-level permission.bash=ask, vgxness-manager with bash=allow, and hidden vgxness-sdd-* agents with explicit permissions; use --mcp-only for legacy MCP-only config.
72
+ OpenCode install defaults to user-global scope and installs mcp.vgxness plus top-level permission.bash=ask, vgxness-manager with bash=deny and native repo tools disabled, and hidden vgxness-sdd-* agents with explicit permissions; use --mcp-only for legacy MCP-only config.
73
73
  Use --overwrite-vgxness (alias --reinstall) to reinstall only VGXNESS-managed OpenCode entries while preserving unrelated config; --yes is still required to write.
74
74
  It writes only after --yes. VGX-managed provider configuration is user-global only for OpenCode and Claude; the OpenCode target is $HOME/.config/opencode/opencode.json.
75
75
  Project/local provider files are external/manual diagnostics and will not be written by VGXNESS. Plans are read-only; applies refuse unsafe existing user-global config and create backups before merge.
@@ -365,7 +365,7 @@ function focusedProviderLines(plan) {
365
365
  ];
366
366
  }
367
367
  function bashPolicySummary(policy) {
368
- return policy.manager === 'allow' ? `${policy.topLevel} globally, allow for VGXNESS manager` : policy.topLevel;
368
+ return policy.manager === 'deny' ? `${policy.topLevel} globally, deny for VGXNESS manager` : policy.topLevel;
369
369
  }
370
370
  function summaryItems(plan) {
371
371
  const ready = plan.status === 'ready';
@@ -197,7 +197,7 @@ function warningsForScope(scope, overwriteVgxness, agentPlan, bashPermissionPoli
197
197
  `Reinstall/overwrite is enabled: existing VGXNESS-managed OpenCode entries will be replaced (${agentPlan.installsAgents ? 'mcp.vgxness, default_agent, instructions AGENTS.md, and known VGXNESS agents' : 'mcp.vgxness only'}); unrelated OpenCode config is preserved.`,
198
198
  ]
199
199
  : [];
200
- const bashWarnings = bashPermissionPolicy.manager === 'allow' ? ['OpenCode top-level permission.bash is set to ask; the VGXNESS manager agent allows bash while SDD subagents keep explicit permissions.'] : ['OpenCode top-level permission.bash is set to ask.'];
200
+ const bashWarnings = bashPermissionPolicy.manager === 'deny' ? ['OpenCode top-level permission.bash is set to ask; the VGXNESS manager agent denies bash while SDD subagents keep explicit phase permissions.'] : ['OpenCode top-level permission.bash is set to ask.'];
201
201
  return [
202
202
  'Restart OpenCode after installation so it reloads the user MCP config.',
203
203
  'OpenCode project config may override user config for a workspace; check project-level config if vgxness is not visible.',
@@ -206,7 +206,7 @@ function warningsForScope(scope, overwriteVgxness, agentPlan, bashPermissionPoli
206
206
  ];
207
207
  }
208
208
  function bashPermissionPolicyFor(agentPlan) {
209
- return agentPlan.installsAgents ? { topLevel: 'ask', manager: 'allow' } : { topLevel: 'ask' };
209
+ return agentPlan.installsAgents ? { topLevel: 'ask', manager: 'deny' } : { topLevel: 'ask' };
210
210
  }
211
211
  function createdConfigKeys(agentPlan) {
212
212
  const keys = agentPlan.installsAgents ? ['$schema', 'instructions', 'default_agent', 'agent', 'mcp'] : ['$schema', 'mcp'];
@@ -214,7 +214,7 @@ function confirmationRequiredMessage(scope) {
214
214
  return `\`mcp install opencode\` requires explicit --yes before any ${scope} config write.`;
215
215
  }
216
216
  function warnings() {
217
- return ['Restart OpenCode after installation so it reloads the project MCP config.', 'OpenCode top-level permission.bash is set to ask; the VGXNESS manager agent allows bash while SDD subagents keep explicit permissions.'];
217
+ return ['Restart OpenCode after installation so it reloads the project MCP config.', 'OpenCode top-level permission.bash is set to ask; the VGXNESS manager agent denies bash while SDD subagents keep explicit phase permissions.'];
218
218
  }
219
219
  function manualTest(databasePath, source) {
220
220
  return {
@@ -231,5 +231,5 @@ function defaultVerificationHints(databasePath, source) {
231
231
  ];
232
232
  }
233
233
  function bashPermissionPolicyFor(agentPlan) {
234
- return agentPlan.installsAgents ? { topLevel: 'ask', manager: 'allow' } : { topLevel: 'ask' };
234
+ return agentPlan.installsAgents ? { topLevel: 'ask', manager: 'deny' } : { topLevel: 'ask' };
235
235
  }
@@ -1,3 +1,4 @@
1
+ import { ActiveWorkPreviewService } from '../memory/active-work-preview.js';
1
2
  import { ContextBudgetService } from '../payload/context-budget-service.js';
2
3
  export const contextCockpitSnapshotLevels = ['compact', 'expanded', 'verbose'];
3
4
  export class ContextCockpitSnapshotService {
@@ -22,11 +23,21 @@ export class ContextCockpitSnapshotService {
22
23
  return sddResult;
23
24
  const sdd = sddResult?.value;
24
25
  const integratesSdd = sdd !== undefined;
26
+ const searchObservationPreviewsNoTrace = this.dependencies.memory.searchObservationPreviewsNoTrace === undefined
27
+ ? (() => ({ ok: true, value: [] }))
28
+ : (filters) => this.dependencies.memory.searchObservationPreviewsNoTrace(filters);
29
+ const activeWorkResult = new ActiveWorkPreviewService({
30
+ searchObservationPreviewsNoTrace,
31
+ }).build({ project: input.project, ...(input.change === undefined ? {} : { change: input.change }) });
32
+ if (!activeWorkResult.ok)
33
+ return activeWorkResult;
34
+ const activeWork = activeWorkResult.value;
25
35
  const optionalSectionsOmitted = omittedSections(legacy.value.optionalSectionsOmitted, integratesSdd ? ['provider'] : ['sdd', 'provider'], integratesSdd ? ['sdd'] : []);
26
36
  const snapshotWithoutBudget = {
27
37
  ...legacy.value,
28
38
  optionalSectionsOmitted,
29
39
  ...(sdd === undefined ? {} : { sdd }),
40
+ ...(activeWork.items.length === 0 && activeWork.change === undefined ? {} : { activeWork }),
30
41
  snapshotVersion: 2,
31
42
  level,
32
43
  references: [
@@ -0,0 +1,75 @@
1
+ import { activeWorkCurrentSegments, activeWorkRecommendedMemoryTypes, activeWorkTopicRoot, parseActiveWorkTopicKey } from './active-work-topics.js';
2
+ const sourceLimit = 50;
3
+ const maxItems = 8;
4
+ const maxPreviewChars = 160;
5
+ const currentPriority = new Map(activeWorkCurrentSegments.map((segment, index) => [segment, index]));
6
+ export class ActiveWorkPreviewService {
7
+ source;
8
+ constructor(source) {
9
+ this.source = source;
10
+ }
11
+ build(input) {
12
+ const found = this.source.searchObservationPreviewsNoTrace({ project: input.project, topicKey: activeWorkTopicRoot, limit: sourceLimit });
13
+ if (!found.ok)
14
+ return found;
15
+ return { ok: true, value: formatActiveWorkPreview(found.value, input.change) };
16
+ }
17
+ }
18
+ export function formatActiveWorkPreview(records, change) {
19
+ const parsed = records
20
+ .map((record) => ({ record, parsed: parseActiveWorkTopicKey(record.topicKey) }))
21
+ .filter((entry) => entry.parsed !== undefined)
22
+ .filter((entry) => change === undefined || entry.parsed.change === change);
23
+ const changes = [...new Set(parsed.map((entry) => entry.parsed.change))].sort();
24
+ const notes = [
25
+ 'Active-work memory is advisory only; canonical SDD status/readiness, runs, artifacts, and explicit human acceptance remain authoritative.',
26
+ 'Previews are bounded and may omit full context; use memory_get for a selected memory when safe.',
27
+ 'Do not store secrets, credentials, raw large logs, hidden reasoning, or sensitive user data in active-work memory.',
28
+ ];
29
+ if (change === undefined && changes.length > 1) {
30
+ return {
31
+ advisory: true,
32
+ items: [],
33
+ truncated: true,
34
+ notes: [...notes, `Multiple active-work changes found (${changes.join(', ')}); provide an explicit change to avoid mixing narratives.`],
35
+ };
36
+ }
37
+ const resolvedChange = change ?? changes[0];
38
+ const candidates = parsed.filter((entry) => resolvedChange === undefined || entry.parsed.change === resolvedChange).sort(compareActiveWorkEntries);
39
+ const items = candidates.slice(0, maxItems).map(({ record }) => toPreviewItem(record));
40
+ const truncated = candidates.length > items.length || records.length >= sourceLimit;
41
+ return {
42
+ advisory: true,
43
+ ...(resolvedChange === undefined ? {} : { change: resolvedChange }),
44
+ items,
45
+ truncated,
46
+ notes: items.length === 0 ? [...notes, 'No active-work memory found for this context.'] : notes,
47
+ };
48
+ }
49
+ function compareActiveWorkEntries(left, right) {
50
+ const leftPriority = currentPriority.get(left.parsed.segment) ?? 100;
51
+ const rightPriority = currentPriority.get(right.parsed.segment) ?? 100;
52
+ if (leftPriority !== rightPriority)
53
+ return leftPriority - rightPriority;
54
+ const expectedLeft = activeWorkRecommendedMemoryTypes[left.parsed.segment] === left.record.type ? 0 : 1;
55
+ const expectedRight = activeWorkRecommendedMemoryTypes[right.parsed.segment] === right.record.type ? 0 : 1;
56
+ if (expectedLeft !== expectedRight)
57
+ return expectedLeft - expectedRight;
58
+ return right.record.updatedAt.localeCompare(left.record.updatedAt);
59
+ }
60
+ function toPreviewItem(record) {
61
+ return {
62
+ id: record.id,
63
+ topicKey: record.topicKey ?? '',
64
+ title: record.title,
65
+ type: record.type,
66
+ preview: redactSensitivePreview(record.preview).slice(0, maxPreviewChars),
67
+ ...(record.updatedAt === undefined ? {} : { updatedAt: record.updatedAt }),
68
+ };
69
+ }
70
+ function redactSensitivePreview(value) {
71
+ return value
72
+ .replace(/\b([A-Za-z0-9._%+-]+)@([A-Za-z0-9.-]+\.[A-Za-z]{2,})\b/g, '[redacted-email]')
73
+ .replace(/\b(api[_-]?key|token|secret|password|credential)\s*[:=]\s*\S+/gi, '$1=[redacted]')
74
+ .replace(/\b(sk-[A-Za-z0-9_-]{8,}|ghp_[A-Za-z0-9_]{8,})\b/g, '[redacted-token]');
75
+ }
@@ -0,0 +1,50 @@
1
+ export const activeWorkTopicRoot = 'active-work';
2
+ export const activeWorkCurrentSegments = ['summary', 'progress', 'next', 'blockers'];
3
+ export const activeWorkDetailSegments = ['decision', 'discovery', 'bugfix', 'config'];
4
+ export const activeWorkRecommendedMemoryTypes = {
5
+ summary: 'manual',
6
+ progress: 'manual',
7
+ next: 'manual',
8
+ blockers: 'manual',
9
+ decision: 'decision',
10
+ discovery: 'discovery',
11
+ bugfix: 'bugfix',
12
+ config: 'config',
13
+ };
14
+ export function activeWorkTopicPrefix(change) {
15
+ return `${activeWorkTopicRoot}/${normalizeTopicPart(change)}`;
16
+ }
17
+ export function activeWorkCurrentTopic(change, segment) {
18
+ return `${activeWorkTopicPrefix(change)}/${segment}`;
19
+ }
20
+ export function activeWorkDetailTopic(change, segment, slug) {
21
+ return `${activeWorkTopicPrefix(change)}/${segment}/${normalizeTopicPart(slug)}`;
22
+ }
23
+ export function parseActiveWorkTopicKey(topicKey) {
24
+ const parts = topicKey?.split('/') ?? [];
25
+ if (parts[0] !== activeWorkTopicRoot || parts.length < 3)
26
+ return undefined;
27
+ const segment = parts[2];
28
+ if (!isActiveWorkSegment(segment))
29
+ return undefined;
30
+ if (isActiveWorkCurrentSegment(segment) && parts.length === 3)
31
+ return { change: parts[1], segment };
32
+ if (isActiveWorkDetailSegment(segment) && parts.length >= 4)
33
+ return { change: parts[1], segment, slug: parts.slice(3).join('/') };
34
+ return undefined;
35
+ }
36
+ function isActiveWorkSegment(value) {
37
+ return isActiveWorkCurrentSegment(value) || isActiveWorkDetailSegment(value);
38
+ }
39
+ function isActiveWorkCurrentSegment(value) {
40
+ return activeWorkCurrentSegments.includes(value);
41
+ }
42
+ function isActiveWorkDetailSegment(value) {
43
+ return activeWorkDetailSegments.includes(value);
44
+ }
45
+ function normalizeTopicPart(value) {
46
+ const trimmed = value.trim();
47
+ if (trimmed.length === 0 || trimmed.includes('/'))
48
+ throw new Error('Active-work topic parts must be non-empty path-safe values');
49
+ return trimmed;
50
+ }
@@ -61,8 +61,8 @@ export const openCodeSetupAdapter = {
61
61
  writesProviderConfig: false,
62
62
  summary: contract.status === 'would_install'
63
63
  ? contract.overwriteVgxness
64
- ? `OpenCode ${contract.action} reinstall plan is available for user-global provider configuration; confirmed apply overwrites VGXNESS entries only, preserves unrelated config, sets top-level permission.bash=ask and manager bash=allow, and creates managed backups for existing user-global targets.`
65
- : `OpenCode ${contract.action} plan is available for user-global provider configuration; confirmed applies mcp.vgxness, top-level permission.bash=ask, and manager bash=allow, and creates managed VGXNESS backups when merging. Project-local provider files are external/manual diagnostics and will not be modified.`
64
+ ? `OpenCode ${contract.action} reinstall plan is available for user-global provider configuration; confirmed apply overwrites VGXNESS entries only, preserves unrelated config, sets top-level permission.bash=ask and manager bash=deny, and creates managed backups for existing user-global targets.`
65
+ : `OpenCode ${contract.action} plan is available for user-global provider configuration; confirmed applies mcp.vgxness, top-level permission.bash=ask, and manager bash=deny, and creates managed VGXNESS backups when merging. Project-local provider files are external/manual diagnostics and will not be modified.`
66
66
  : contract.message,
67
67
  ...(targetPath !== undefined ? { targetPath } : {}),
68
68
  warnings: contract.warnings,
@@ -155,8 +155,8 @@ function overwriteInstallRisks(installsAgents) {
155
155
  ];
156
156
  }
157
157
  function bashPermissionRisks(policy) {
158
- return policy.manager === 'allow'
159
- ? ['OpenCode top-level permission.bash is set to ask; terminal commands prompt by default, while vgxness-manager allows bash after restart.']
158
+ return policy.manager === 'deny'
159
+ ? ['OpenCode top-level permission.bash is set to ask; terminal commands prompt by default, while vgxness-manager denies bash after restart.']
160
160
  : ['OpenCode top-level permission.bash is set to ask.'];
161
161
  }
162
162
  function refusedRepairRisks(message) {
@@ -187,7 +187,7 @@ function setupPlanFromOpenCode(input) {
187
187
  };
188
188
  }
189
189
  function bashPermissionDescription(policy) {
190
- return policy.manager === 'allow' ? '; sets top-level permission.bash to ask and manager bash to allow' : '; sets top-level permission.bash to ask';
190
+ return policy.manager === 'deny' ? '; sets top-level permission.bash to ask and manager bash to deny' : '; sets top-level permission.bash to ask';
191
191
  }
192
192
  function resolveSetupDatabase(input) {
193
193
  if (input.mode === 'global') {
@@ -1,4 +1,5 @@
1
1
  import { resolveAgentSelector } from '../agents/agent-selector-resolver.js';
2
+ import { canonicalDefaultAgentName } from '../agents/canonical-agent-manifest.js';
2
3
  import { normalizeSddPhaseInput } from '../sdd/schema.js';
3
4
  import { skillLookupContexts } from './personal-skills.js';
4
5
  export class SkillResolver {
@@ -109,14 +110,37 @@ export class SkillResolver {
109
110
  return byId.ok ? ok({ agent: byId.value }) : byId;
110
111
  }
111
112
  const selected = resolveAgentSelector({ selector: input.agentId, project: input.project, scope: input.scope ?? 'project' }, { getById: (id) => this.agents.getById(id), getByName: (project, scope, name) => this.agents.getByName(project, scope, name) });
112
- return selected.ok ? ok({ agent: selected.value.agent, ...(selected.value.warning !== undefined ? { warning: selected.value.warning } : {}) }) : selected;
113
+ if (selected.ok)
114
+ return ok({ agent: selected.value.agent, ...(selected.value.warning !== undefined ? { warning: selected.value.warning } : {}) });
115
+ if (selected.error.code !== 'not_found')
116
+ return selected;
117
+ const fallback = this.resolveAgentNameFallback(input);
118
+ if (fallback.ok && fallback.value.agent !== undefined) {
119
+ const warning = `AGENT_SELECTOR_PROVIDER_ALIAS_FALLBACK: selector "${input.agentId}" did not match a VGXNESS registry agent; resolved display/provider name to canonical agent "${fallback.value.agent.name}" (${fallback.value.agent.id}).`;
120
+ return ok({ agent: fallback.value.agent, warning });
121
+ }
122
+ return selected;
113
123
  }
114
124
  if (input.agentName === undefined)
115
125
  return ok({});
116
126
  if (input.project === undefined)
117
127
  return validationFailure('--project is required when resolving by agent name');
118
- const byName = this.agents.getByName(input.project, input.scope ?? 'project', input.agentName);
119
- return byName.ok ? ok({ agent: byName.value }) : byName;
128
+ return this.resolveAgentNameFallback(input);
129
+ }
130
+ resolveAgentNameFallback(input) {
131
+ if (input.project === undefined)
132
+ return validationFailure('--project is required when resolving by agent name');
133
+ for (const name of providerAgentNameCandidates(input.agentName, input.agentId)) {
134
+ const selected = resolveAgentSelector({ selector: name, project: input.project, scope: input.scope ?? 'project' }, { getById: (id) => this.agents.getById(id), getByName: (project, scope, agentName) => this.agents.getByName(project, scope, agentName) });
135
+ if (selected.ok)
136
+ return ok({ agent: selected.value.agent });
137
+ if (selected.error.code !== 'not_found')
138
+ return selected;
139
+ }
140
+ if (input.agentName === undefined)
141
+ return ok({});
142
+ const exact = this.agents.getByName(input.project, input.scope ?? 'project', input.agentName);
143
+ return exact.ok ? ok({ agent: exact.value }) : exact;
120
144
  }
121
145
  targetCandidates(input, agent) {
122
146
  const targets = [];
@@ -254,6 +278,32 @@ function isSddWorkflow(workflow) {
254
278
  function normalizedIntentSignals(signals) {
255
279
  return [...new Set((signals ?? []).map((signal) => signal.trim().toLowerCase()).filter(Boolean))];
256
280
  }
281
+ function providerAgentNameCandidates(agentName, agentId) {
282
+ const candidates = [];
283
+ const add = (value) => {
284
+ const normalized = value?.trim();
285
+ if (normalized === undefined || normalized.length === 0 || candidates.includes(normalized))
286
+ return;
287
+ candidates.push(normalized);
288
+ };
289
+ add(agentName);
290
+ add(slugAgentDisplayName(agentName));
291
+ if (isProviderManagerAlias(agentId) || isProviderManagerAlias(agentName))
292
+ add(canonicalDefaultAgentName);
293
+ return candidates;
294
+ }
295
+ function slugAgentDisplayName(value) {
296
+ const normalized = value
297
+ ?.trim()
298
+ .toLowerCase()
299
+ .replace(/[^a-z0-9]+/g, '-')
300
+ .replace(/^-+|-+$/g, '');
301
+ return normalized === undefined || normalized.length === 0 ? undefined : normalized;
302
+ }
303
+ function isProviderManagerAlias(value) {
304
+ const normalized = slugAgentDisplayName(value);
305
+ return normalized === 'main-manager' || normalized === 'vgxness-manager' || normalized === 'vgxness-main-manager';
306
+ }
257
307
  function stringMetadata(value) {
258
308
  return typeof value === 'string' && value.trim() ? value : undefined;
259
309
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vgxness",
3
- "version": "1.18.0",
3
+ "version": "1.19.0",
4
4
  "description": "CLI and MCP control plane for guided AI-agent workflows, SDD, memory, and OpenCode setup.",
5
5
  "license": "SEE LICENSE IN LICENSE",
6
6
  "repository": {