vgxness 1.2.0 → 1.3.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.
- package/README.md +7 -6
- package/dist/cli/cli-help.js +8 -7
- package/dist/cli/commands/index.js +1 -1
- package/dist/cli/commands/interactive-entrypoint-dispatcher.js +150 -0
- package/dist/cli/commands/setup-dispatcher.js +7 -4
- package/dist/cli/dispatcher.js +10 -8
- package/dist/cli/index.js +0 -0
- package/dist/cli/setup-wizard-renderer.js +1 -1
- package/dist/cli/tui/main-menu/index.js +0 -1
- package/dist/cli/tui/main-menu/main-menu-controller.js +0 -2
- package/dist/cli/tui/main-menu/main-menu-read-model.js +2 -8
- package/dist/cli/tui/main-menu/main-menu-render-shape.js +5 -1
- package/dist/cli/tui/main-menu/main-menu-state.js +1 -1
- package/dist/cli/tui/opentui/code/index.js +210 -0
- package/dist/cli/tui/opentui/code/screen.js +107 -0
- package/dist/cli/tui/opentui/code/smoke.js +32 -0
- package/dist/cli/tui/opentui/main-menu/index.js +3 -0
- package/dist/cli/tui/opentui/main-menu/renderer.js +68 -0
- package/dist/cli/tui/opentui/main-menu/screen.js +62 -0
- package/dist/cli/tui/opentui/main-menu/smoke.js +17 -0
- package/dist/cli/tui/opentui/main-menu/view.js +8 -0
- package/dist/cli/tui/opentui/setup/index.js +3 -0
- package/dist/cli/tui/opentui/setup/renderer.js +87 -0
- package/dist/cli/tui/opentui/setup/screen.js +170 -0
- package/dist/cli/tui/opentui/setup/smoke.js +42 -0
- package/dist/cli/tui/opentui/setup/view.js +12 -0
- package/dist/cli/tui/setup/setup-tui-input.js +43 -0
- package/dist/cli/tui/setup/setup-tui-read-model.js +1 -1
- package/dist/cli/tui/setup/setup-tui-render-shape.js +1 -1
- package/dist/cli/tui/setup/setup-tui-view-helpers.js +46 -0
- package/dist/cli/tui/visual/index.js +0 -2
- package/dist/code/tui/approval-actions.js +33 -0
- package/dist/code/tui/prompt-mode.js +11 -0
- package/dist/code/tui/runtime-events.js +320 -0
- package/dist/sdd/sdd-workflow-service.js +0 -24
- package/dist/setup/providers/antigravity-setup-adapter.js +1 -1
- package/dist/setup/providers/claude-setup-adapter.js +2 -2
- package/dist/setup/providers/custom-setup-adapter.js +1 -1
- package/dist/setup/providers/opencode-setup-adapter.js +3 -3
- package/dist/setup/setup-lifecycle-service.js +1 -1
- package/docs/architecture.md +4 -4
- package/docs/cli.md +11 -10
- package/docs/funcionamiento-del-sistema.md +6 -7
- package/docs/prd.md +4 -4
- package/docs/vgxcode.md +76 -0
- package/package.json +5 -6
- package/dist/cli/commands/dashboard-dispatcher.js +0 -560
- package/dist/cli/dashboard-operational-read-models.js +0 -428
- package/dist/cli/dashboard-renderer.js +0 -158
- package/dist/cli/dashboard-screen-renderers.js +0 -256
- package/dist/cli/dashboard-tui-read-model.js +0 -73
- package/dist/cli/dashboard-tui-state.js +0 -314
- package/dist/cli/interactive-dashboard.js +0 -34
- package/dist/cli/tui/dashboard/dashboard-adapter.js +0 -4
- package/dist/cli/tui/main-menu/main-menu-app.js +0 -28
- package/dist/cli/tui/render-ink-app.js +0 -10
- package/dist/cli/tui/setup/screens/applying-screen.js +0 -6
- package/dist/cli/tui/setup/screens/cancellation-screen.js +0 -6
- package/dist/cli/tui/setup/screens/error-recovery-screen.js +0 -6
- package/dist/cli/tui/setup/screens/final-confirmation-screen.js +0 -6
- package/dist/cli/tui/setup/screens/opencode-details-screen.js +0 -10
- package/dist/cli/tui/setup/screens/plan-review-screen.js +0 -6
- package/dist/cli/tui/setup/screens/project-database-screen.js +0 -6
- package/dist/cli/tui/setup/screens/provider-screen.js +0 -7
- package/dist/cli/tui/setup/screens/result-screen.js +0 -16
- package/dist/cli/tui/setup/screens/screen-components.js +0 -103
- package/dist/cli/tui/setup/screens/welcome-screen.js +0 -6
- package/dist/cli/tui/setup/setup-tui-app.js +0 -113
- package/dist/cli/tui/visual/choice-list.js +0 -10
- package/dist/cli/tui/visual/layout.js +0 -10
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { navigationIntentFromInput } from '../keymap.js';
|
|
2
|
+
export function setupTuiActionFromInput(input, key, state) {
|
|
3
|
+
if (key.upArrow === true)
|
|
4
|
+
return { type: 'focus-previous-choice' };
|
|
5
|
+
if (key.downArrow === true)
|
|
6
|
+
return { type: 'focus-next-choice' };
|
|
7
|
+
if (key.escape === true)
|
|
8
|
+
return state.helpVisible ? { type: 'toggle-help' } : { type: 'cancel' };
|
|
9
|
+
if (key.tab === true && key.shift === true)
|
|
10
|
+
return { type: 'back' };
|
|
11
|
+
if (key.tab === true)
|
|
12
|
+
return { type: 'continue' };
|
|
13
|
+
const raw = key.return === true ? '\n' : input;
|
|
14
|
+
const intent = navigationIntentFromInput(raw);
|
|
15
|
+
if (intent === 'cancel')
|
|
16
|
+
return state.helpVisible ? { type: 'toggle-help' } : { type: 'cancel' };
|
|
17
|
+
if (intent === 'help')
|
|
18
|
+
return { type: 'toggle-help' };
|
|
19
|
+
if (intent === 'up')
|
|
20
|
+
return { type: 'focus-previous-choice' };
|
|
21
|
+
if (intent === 'down')
|
|
22
|
+
return { type: 'focus-next-choice' };
|
|
23
|
+
if (intent === 'select')
|
|
24
|
+
return { type: 'select-focused-choice' };
|
|
25
|
+
if (intent === 'back')
|
|
26
|
+
return { type: 'back' };
|
|
27
|
+
if (intent !== 'next')
|
|
28
|
+
return undefined;
|
|
29
|
+
if (key.return === true && isEditableChoiceScreen(state))
|
|
30
|
+
return { type: 'select-focused-choice' };
|
|
31
|
+
if (state.screen === 'plan-review')
|
|
32
|
+
return { type: 'request-apply' };
|
|
33
|
+
if (state.screen === 'final-confirmation')
|
|
34
|
+
return { type: 'confirm-apply' };
|
|
35
|
+
return { type: 'continue' };
|
|
36
|
+
}
|
|
37
|
+
function isEditableChoiceScreen(state) {
|
|
38
|
+
if (state.focusedChoiceId === undefined)
|
|
39
|
+
return false;
|
|
40
|
+
if (state.screen === 'project-database' || state.screen === 'provider')
|
|
41
|
+
return true;
|
|
42
|
+
return state.screen === 'opencode-details' && state.selections.provider === 'opencode';
|
|
43
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { badgeLabels, compactPath, formatFooter, setupFooterHints, wrapLabel } from './
|
|
1
|
+
import { badgeLabels, compactPath, formatFooter, setupFooterHints, wrapLabel } from './setup-tui-view-helpers.js';
|
|
2
2
|
export function renderSetupTuiShape(input) {
|
|
3
3
|
const width = input.width ?? 80;
|
|
4
4
|
const lines = [input.viewModel.title, ...screenLines(input, width)];
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { formatTuiFooter } from '../visual/footer.js';
|
|
2
|
+
export const badgeLabels = {
|
|
3
|
+
recommended: '[recommended]',
|
|
4
|
+
selected: '[selected]',
|
|
5
|
+
focused: '[focused]',
|
|
6
|
+
warning: '[warning]',
|
|
7
|
+
error: '[error]',
|
|
8
|
+
writeAfterConfirm: '[will write after confirm]',
|
|
9
|
+
readOnly: '[read-only]',
|
|
10
|
+
manual: '[manual]',
|
|
11
|
+
deferred: '[deferred]',
|
|
12
|
+
cancelled: '[cancelled]',
|
|
13
|
+
pending: '[pending]',
|
|
14
|
+
success: '[success]',
|
|
15
|
+
};
|
|
16
|
+
export const setupFooterHints = {
|
|
17
|
+
continue: { key: 'Enter', label: 'continue' },
|
|
18
|
+
select: { key: 'Enter/Space', label: 'select focused row' },
|
|
19
|
+
finalConfirmation: { key: 'Enter', label: 'final confirmation' },
|
|
20
|
+
confirmApply: { key: 'Enter', label: 'confirm and apply' },
|
|
21
|
+
help: { key: '?/h', label: 'help' },
|
|
22
|
+
back: { key: 'Shift+Tab', label: 'back' },
|
|
23
|
+
cancel: { key: 'q/Esc', label: 'cancel' },
|
|
24
|
+
close: { key: 'q/Esc', label: 'close' },
|
|
25
|
+
};
|
|
26
|
+
export function compactPath(path, width) {
|
|
27
|
+
if (width <= 0 || path.length <= width)
|
|
28
|
+
return path;
|
|
29
|
+
const basename = path.split(/[\\/]/).filter(Boolean).pop() ?? path;
|
|
30
|
+
if (basename.length + 4 >= width)
|
|
31
|
+
return `.../${basename}`;
|
|
32
|
+
const prefixWidth = Math.max(1, width - basename.length - 5);
|
|
33
|
+
return `${path.slice(0, prefixWidth)}.../${basename}`;
|
|
34
|
+
}
|
|
35
|
+
export function wrapLabel(value, width) {
|
|
36
|
+
if (width <= 0 || value.length <= width)
|
|
37
|
+
return value;
|
|
38
|
+
const protectedPhrases = ['final confirmation', 'confirm and apply', 'not installable', 'read-only', 'requires confirmation', 'will write after confirm', 'error', 'cancelled'];
|
|
39
|
+
const phrase = protectedPhrases.find((candidate) => value.includes(candidate));
|
|
40
|
+
if (phrase !== undefined)
|
|
41
|
+
return `${value.slice(0, Math.max(0, width - phrase.length - 5)).trim()} ... ${phrase}`.trim();
|
|
42
|
+
return `${value.slice(0, Math.max(0, width - 1)).trim()}…`;
|
|
43
|
+
}
|
|
44
|
+
export function formatFooter(hints, width) {
|
|
45
|
+
return formatTuiFooter(hints, width);
|
|
46
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
export function getApprovalActionsState(input) {
|
|
2
|
+
if (input.source !== 'interactive' || input.status !== 'running' || input.mode !== 'craft' || !input.childStdinOpen)
|
|
3
|
+
return { enabled: false };
|
|
4
|
+
const approval = input.latestPendingApproval;
|
|
5
|
+
if (input.pendingApprovalCount < 1 || approval?.approvalId === undefined || approval.toolCallId === undefined)
|
|
6
|
+
return { enabled: false };
|
|
7
|
+
return { enabled: true, approval };
|
|
8
|
+
}
|
|
9
|
+
export class ApprovalDecisionWriter {
|
|
10
|
+
#sink;
|
|
11
|
+
#writtenKeys = new Set();
|
|
12
|
+
constructor(sink) {
|
|
13
|
+
this.#sink = sink;
|
|
14
|
+
}
|
|
15
|
+
write(approval, status, reason) {
|
|
16
|
+
if (approval?.approvalId === undefined || approval.toolCallId === undefined)
|
|
17
|
+
return false;
|
|
18
|
+
if (this.#sink.writableEnded === true || this.#sink.destroyed === true)
|
|
19
|
+
return false;
|
|
20
|
+
const key = `${approval.approvalId}:${approval.toolCallId}`;
|
|
21
|
+
if (this.#writtenKeys.has(key))
|
|
22
|
+
return false;
|
|
23
|
+
this.#sink.write(`${JSON.stringify({
|
|
24
|
+
type: 'approval.decision',
|
|
25
|
+
approvalId: approval.approvalId,
|
|
26
|
+
toolCallId: approval.toolCallId,
|
|
27
|
+
status,
|
|
28
|
+
reason,
|
|
29
|
+
})}\n`);
|
|
30
|
+
this.#writtenKeys.add(key);
|
|
31
|
+
return true;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export function parsePromptSubmission(input, currentMode) {
|
|
2
|
+
const trimmed = input.trim();
|
|
3
|
+
const prefixMatch = /^(\/(inspect|plan|craft|craft-preview))(?:\s+|$)/iu.exec(trimmed);
|
|
4
|
+
if (!prefixMatch)
|
|
5
|
+
return { mode: currentMode, prompt: trimmed };
|
|
6
|
+
const mode = prefixMatch[2]?.toLowerCase();
|
|
7
|
+
return { mode, prompt: trimmed.slice(prefixMatch[0].length).trim() };
|
|
8
|
+
}
|
|
9
|
+
export function toggleReadOnlyMode(mode) {
|
|
10
|
+
return mode === 'inspect' ? 'plan' : 'inspect';
|
|
11
|
+
}
|
|
@@ -0,0 +1,320 @@
|
|
|
1
|
+
export function sampleEvents(now = new Date('2026-05-26T23:30:00.000Z')) {
|
|
2
|
+
const createdAt = now.toISOString();
|
|
3
|
+
return [
|
|
4
|
+
{
|
|
5
|
+
type: 'session.started',
|
|
6
|
+
projectRoot: 'vgxness',
|
|
7
|
+
userIntent: 'Build the vgxcode OpenTUI shell',
|
|
8
|
+
createdAt,
|
|
9
|
+
},
|
|
10
|
+
{
|
|
11
|
+
type: 'provider.message',
|
|
12
|
+
content: 'I will inspect the runtime event stream and prepare a safe UI shell.',
|
|
13
|
+
toolCallCount: 1,
|
|
14
|
+
createdAt,
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
type: 'tool.requested',
|
|
18
|
+
toolName: 'read_file',
|
|
19
|
+
createdAt,
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
type: 'tool.completed',
|
|
23
|
+
toolName: 'read_file',
|
|
24
|
+
ok: true,
|
|
25
|
+
changedFiles: [],
|
|
26
|
+
createdAt,
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
type: 'verification.completed',
|
|
30
|
+
status: 'not-run',
|
|
31
|
+
reason: 'Static shell preview; no runtime commands executed.',
|
|
32
|
+
createdAt,
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
type: 'session.completed',
|
|
36
|
+
status: 'completed',
|
|
37
|
+
outcome: 'success',
|
|
38
|
+
createdAt,
|
|
39
|
+
},
|
|
40
|
+
];
|
|
41
|
+
}
|
|
42
|
+
export function parseVgxcodeJsonl(input) {
|
|
43
|
+
const events = [];
|
|
44
|
+
const errors = [];
|
|
45
|
+
const lines = input.split(/\r?\n/);
|
|
46
|
+
for (const [index, line] of lines.entries()) {
|
|
47
|
+
const trimmed = line.trim();
|
|
48
|
+
if (!trimmed)
|
|
49
|
+
continue;
|
|
50
|
+
try {
|
|
51
|
+
const parsed = JSON.parse(trimmed);
|
|
52
|
+
if (isVgxcodeEvent(parsed))
|
|
53
|
+
events.push(parsed);
|
|
54
|
+
else
|
|
55
|
+
errors.push(formatUnsupportedRuntimeEvent(parsed, index + 1));
|
|
56
|
+
}
|
|
57
|
+
catch (error) {
|
|
58
|
+
errors.push(`Line ${index + 1}: ${formatJsonlParseError(trimmed, error)}`);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
return { events, errors };
|
|
62
|
+
}
|
|
63
|
+
export function parseVgxcodeJsonlLine(line, lineNumber) {
|
|
64
|
+
const trimmed = line.trim();
|
|
65
|
+
if (!trimmed)
|
|
66
|
+
return {};
|
|
67
|
+
try {
|
|
68
|
+
const parsed = JSON.parse(trimmed);
|
|
69
|
+
if (isVgxcodeEvent(parsed))
|
|
70
|
+
return { event: parsed };
|
|
71
|
+
return { error: formatUnsupportedRuntimeEvent(parsed, lineNumber) };
|
|
72
|
+
}
|
|
73
|
+
catch (error) {
|
|
74
|
+
return { error: `Line ${lineNumber}: ${formatJsonlParseError(trimmed, error)}` };
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
function formatJsonlParseError(line, error) {
|
|
78
|
+
const reason = error instanceof Error ? error.message : 'invalid JSON';
|
|
79
|
+
const looksLikeNpmBanner = line.startsWith('>') || line.includes('npm') || line.includes('run-script');
|
|
80
|
+
const snippet = line.length > 80 ? `${line.slice(0, 77)}...` : line;
|
|
81
|
+
return looksLikeNpmBanner
|
|
82
|
+
? `${reason} (non-JSON output; use npm --silent for the bridge; got: ${snippet})`
|
|
83
|
+
: `${reason} (invalid JSONL; got: ${snippet})`;
|
|
84
|
+
}
|
|
85
|
+
function formatUnsupportedRuntimeEvent(value, lineNumber) {
|
|
86
|
+
const type = isRecord(value) && typeof value.type === 'string' ? value.type : 'missing type';
|
|
87
|
+
return `Line ${lineNumber}: unsupported runtime event (${type}); expected vgxcode read-only JSONL event`;
|
|
88
|
+
}
|
|
89
|
+
export function summarizeEvent(event) {
|
|
90
|
+
switch (event.type) {
|
|
91
|
+
case 'session.started':
|
|
92
|
+
return `◇ Session started — ${event.userIntent}`;
|
|
93
|
+
case 'prompt.accepted':
|
|
94
|
+
return `◇ Prompt accepted — ${event.messageCount} messages, ${event.toolCount} tools`;
|
|
95
|
+
case 'provider.message':
|
|
96
|
+
return `● Assistant — ${event.content}`;
|
|
97
|
+
case 'tool.requested':
|
|
98
|
+
return `▸ Tool requested — ${event.toolName}`;
|
|
99
|
+
case 'approval.pending':
|
|
100
|
+
return `! Approval pending — ${event.toolName}`;
|
|
101
|
+
case 'approval.decided':
|
|
102
|
+
return `! Approval ${event.final} — ${event.toolName}: ${event.reason}`;
|
|
103
|
+
case 'approval.decision_rejected':
|
|
104
|
+
return `! Approval decision rejected — ${event.bindingResult}: ${event.reason}`;
|
|
105
|
+
case 'approval.preview':
|
|
106
|
+
return `! Approval preview — ${event.toolName}: No mutation executed`;
|
|
107
|
+
case 'tool.completed':
|
|
108
|
+
return `${event.ok ? '✓' : '✕'} Tool completed — ${event.toolName}`;
|
|
109
|
+
case 'diff.preview':
|
|
110
|
+
return `◇ Diff preview — ${event.files.length} files, ${event.bodyBytes}/${event.originalBytes} bytes${event.truncated ? ' (truncated)' : ''}`;
|
|
111
|
+
case 'verification.completed':
|
|
112
|
+
return `${event.status === 'passed' ? '✓' : event.status === 'failed' ? '✕' : '·'} Verification — ${event.reason}`;
|
|
113
|
+
case 'session.completed':
|
|
114
|
+
return `◆ Session ${event.status} — ${event.outcome}`;
|
|
115
|
+
case 'session.failed':
|
|
116
|
+
return `✕ Session ${event.status} — ${event.message}`;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
export function buildSafetyReadModel(events) {
|
|
120
|
+
const pendingApprovals = new Map();
|
|
121
|
+
let latestApprovalDecision;
|
|
122
|
+
let latestApprovalPreview;
|
|
123
|
+
const changedFiles = [];
|
|
124
|
+
const seenChangedFiles = new Set();
|
|
125
|
+
let latestDiffPreview;
|
|
126
|
+
for (const event of events) {
|
|
127
|
+
switch (event.type) {
|
|
128
|
+
case 'approval.pending':
|
|
129
|
+
if (event.approvalId === undefined || event.toolCallId === undefined)
|
|
130
|
+
break;
|
|
131
|
+
pendingApprovals.set(approvalKey(event.approvalId, event.toolCallId), {
|
|
132
|
+
...(event.approvalId === undefined ? {} : { approvalId: event.approvalId }),
|
|
133
|
+
...(event.toolCallId === undefined ? {} : { toolCallId: event.toolCallId }),
|
|
134
|
+
toolName: event.toolName,
|
|
135
|
+
...(event.operationSummary === undefined ? {} : { operationSummary: event.operationSummary }),
|
|
136
|
+
...(event.targetPath === undefined ? {} : { targetPath: event.targetPath }),
|
|
137
|
+
...(event.risk === undefined ? {} : { risk: event.risk }),
|
|
138
|
+
createdAt: event.createdAt,
|
|
139
|
+
});
|
|
140
|
+
break;
|
|
141
|
+
case 'approval.decided':
|
|
142
|
+
if (event.approvalId !== undefined && event.toolCallId !== undefined)
|
|
143
|
+
pendingApprovals.delete(approvalKey(event.approvalId, event.toolCallId));
|
|
144
|
+
latestApprovalDecision = {
|
|
145
|
+
...(event.approvalId === undefined ? {} : { approvalId: event.approvalId }),
|
|
146
|
+
...(event.toolCallId === undefined ? {} : { toolCallId: event.toolCallId }),
|
|
147
|
+
toolName: event.toolName,
|
|
148
|
+
...(event.status === undefined ? {} : { status: event.status }),
|
|
149
|
+
final: event.final,
|
|
150
|
+
reason: event.reason,
|
|
151
|
+
createdAt: event.createdAt,
|
|
152
|
+
};
|
|
153
|
+
break;
|
|
154
|
+
case 'approval.decision_rejected':
|
|
155
|
+
if (event.approvalId !== undefined && event.toolCallId !== undefined && isResolvingDecisionRejection(event.bindingResult)) {
|
|
156
|
+
pendingApprovals.delete(approvalKey(event.approvalId, event.toolCallId));
|
|
157
|
+
}
|
|
158
|
+
break;
|
|
159
|
+
case 'approval.preview':
|
|
160
|
+
latestApprovalPreview = {
|
|
161
|
+
toolName: event.toolName,
|
|
162
|
+
capability: event.capability,
|
|
163
|
+
...(event.targetPath === undefined ? {} : { targetPath: event.targetPath }),
|
|
164
|
+
reason: event.reason,
|
|
165
|
+
risk: event.risk,
|
|
166
|
+
createdAt: event.createdAt,
|
|
167
|
+
};
|
|
168
|
+
break;
|
|
169
|
+
case 'tool.completed':
|
|
170
|
+
for (const changedFile of event.changedFiles) {
|
|
171
|
+
if (seenChangedFiles.has(changedFile))
|
|
172
|
+
continue;
|
|
173
|
+
seenChangedFiles.add(changedFile);
|
|
174
|
+
changedFiles.push(changedFile);
|
|
175
|
+
}
|
|
176
|
+
break;
|
|
177
|
+
case 'diff.preview':
|
|
178
|
+
latestDiffPreview = {
|
|
179
|
+
files: event.files,
|
|
180
|
+
body: event.body,
|
|
181
|
+
bodyBytes: event.bodyBytes,
|
|
182
|
+
originalBytes: event.originalBytes,
|
|
183
|
+
truncated: event.truncated,
|
|
184
|
+
redacted: event.redacted,
|
|
185
|
+
hash: event.hash,
|
|
186
|
+
createdAt: event.createdAt,
|
|
187
|
+
};
|
|
188
|
+
for (const file of event.files) {
|
|
189
|
+
if (seenChangedFiles.has(file))
|
|
190
|
+
continue;
|
|
191
|
+
seenChangedFiles.add(file);
|
|
192
|
+
changedFiles.push(file);
|
|
193
|
+
}
|
|
194
|
+
break;
|
|
195
|
+
default:
|
|
196
|
+
break;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
const pendingApprovalValues = [...pendingApprovals.values()];
|
|
200
|
+
const latestPendingApproval = pendingApprovalValues.at(-1);
|
|
201
|
+
return {
|
|
202
|
+
pendingApprovals: pendingApprovalValues,
|
|
203
|
+
pendingApprovalCount: pendingApprovalValues.length,
|
|
204
|
+
...(latestPendingApproval ? { latestPendingApproval } : {}),
|
|
205
|
+
...(latestApprovalDecision ? { latestApprovalDecision } : {}),
|
|
206
|
+
...(latestApprovalPreview ? { latestApprovalPreview } : {}),
|
|
207
|
+
changedFiles,
|
|
208
|
+
diffAvailability: latestDiffPreview !== undefined ? 'preview' : changedFiles.length > 0 ? 'filenames-only' : 'none',
|
|
209
|
+
...(latestDiffPreview ? { latestDiffPreview } : {}),
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
function isVgxcodeEvent(value) {
|
|
213
|
+
if (!isRecord(value) || typeof value.type !== 'string' || typeof value.createdAt !== 'string')
|
|
214
|
+
return false;
|
|
215
|
+
switch (value.type) {
|
|
216
|
+
case 'session.started':
|
|
217
|
+
return typeof value.projectRoot === 'string' && typeof value.userIntent === 'string';
|
|
218
|
+
case 'prompt.accepted':
|
|
219
|
+
return typeof value.messageCount === 'number' && typeof value.toolCount === 'number';
|
|
220
|
+
case 'provider.message':
|
|
221
|
+
return typeof value.content === 'string' && typeof value.toolCallCount === 'number';
|
|
222
|
+
case 'tool.requested':
|
|
223
|
+
return typeof value.toolName === 'string';
|
|
224
|
+
case 'approval.pending':
|
|
225
|
+
return typeof value.toolName === 'string' && isOptionalApprovalContext(value);
|
|
226
|
+
case 'approval.decided':
|
|
227
|
+
return (typeof value.toolName === 'string' &&
|
|
228
|
+
typeof value.reason === 'string' &&
|
|
229
|
+
isApprovalFinal(value.final) &&
|
|
230
|
+
(value.approvalId === undefined || typeof value.approvalId === 'string') &&
|
|
231
|
+
(value.toolCallId === undefined || typeof value.toolCallId === 'string') &&
|
|
232
|
+
(value.status === undefined || isApprovalStatus(value.status)));
|
|
233
|
+
case 'approval.decision_rejected':
|
|
234
|
+
return (typeof value.reason === 'string' &&
|
|
235
|
+
isApprovalDecisionBindingResult(value.bindingResult) &&
|
|
236
|
+
(value.approvalId === undefined || typeof value.approvalId === 'string') &&
|
|
237
|
+
(value.toolCallId === undefined || typeof value.toolCallId === 'string') &&
|
|
238
|
+
(value.status === undefined || value.status === 'approved' || value.status === 'denied'));
|
|
239
|
+
case 'approval.preview':
|
|
240
|
+
return (typeof value.toolCallId === 'string' &&
|
|
241
|
+
typeof value.toolName === 'string' &&
|
|
242
|
+
isCapability(value.capability) &&
|
|
243
|
+
isRecord(value.risk) &&
|
|
244
|
+
typeof value.risk.mutatesWorkspace === 'boolean' &&
|
|
245
|
+
typeof value.risk.requiresApproval === 'boolean' &&
|
|
246
|
+
value.reason === 'preview_only' &&
|
|
247
|
+
(value.targetPath === undefined || typeof value.targetPath === 'string'));
|
|
248
|
+
case 'tool.completed':
|
|
249
|
+
return typeof value.toolName === 'string' && typeof value.ok === 'boolean' && Array.isArray(value.changedFiles) && value.changedFiles.every((changedFile) => typeof changedFile === 'string');
|
|
250
|
+
case 'diff.preview':
|
|
251
|
+
return (typeof value.sourceToolCallId === 'string' &&
|
|
252
|
+
value.sourceToolName === 'git_diff' &&
|
|
253
|
+
value.format === 'unified' &&
|
|
254
|
+
Array.isArray(value.files) &&
|
|
255
|
+
value.files.every((file) => typeof file === 'string') &&
|
|
256
|
+
typeof value.body === 'string' &&
|
|
257
|
+
typeof value.bodyBytes === 'number' &&
|
|
258
|
+
typeof value.originalBytes === 'number' &&
|
|
259
|
+
typeof value.truncated === 'boolean' &&
|
|
260
|
+
typeof value.redacted === 'boolean' &&
|
|
261
|
+
typeof value.hash === 'string');
|
|
262
|
+
case 'verification.completed':
|
|
263
|
+
return isVerificationStatus(value.status) && typeof value.reason === 'string';
|
|
264
|
+
case 'session.completed':
|
|
265
|
+
return isSessionStatus(value.status) && isOutcome(value.outcome);
|
|
266
|
+
case 'session.failed':
|
|
267
|
+
return isFailedSessionStatus(value.status) && typeof value.message === 'string';
|
|
268
|
+
default:
|
|
269
|
+
return false;
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
function isRecord(value) {
|
|
273
|
+
return typeof value === 'object' && value !== null;
|
|
274
|
+
}
|
|
275
|
+
function isApprovalFinal(value) {
|
|
276
|
+
return value === 'allow' || value === 'deny' || value === 'blocked';
|
|
277
|
+
}
|
|
278
|
+
function isApprovalStatus(value) {
|
|
279
|
+
return value === 'approved' || value === 'denied' || value === 'blocked' || value === 'cancelled' || value === 'timed_out';
|
|
280
|
+
}
|
|
281
|
+
function isApprovalDecisionBindingResult(value) {
|
|
282
|
+
return value === 'malformed' || value === 'unknown' || value === 'binding_mismatch' || value === 'duplicate' || value === 'stale' || value === 'invalid_status' || value === 'cancelled';
|
|
283
|
+
}
|
|
284
|
+
function isResolvingDecisionRejection(bindingResult) {
|
|
285
|
+
return bindingResult === 'duplicate' || bindingResult === 'stale' || bindingResult === 'cancelled';
|
|
286
|
+
}
|
|
287
|
+
function approvalKey(approvalId, toolCallId) {
|
|
288
|
+
return `${approvalId}:${toolCallId}`;
|
|
289
|
+
}
|
|
290
|
+
function isOptionalApprovalContext(value) {
|
|
291
|
+
return ((value.approvalId === undefined || typeof value.approvalId === 'string') &&
|
|
292
|
+
(value.toolCallId === undefined || typeof value.toolCallId === 'string') &&
|
|
293
|
+
(value.operationSummary === undefined || typeof value.operationSummary === 'string') &&
|
|
294
|
+
(value.targetPath === undefined || typeof value.targetPath === 'string') &&
|
|
295
|
+
(value.risk === undefined || isApprovalRisk(value.risk)));
|
|
296
|
+
}
|
|
297
|
+
function isApprovalRisk(value) {
|
|
298
|
+
return (isRecord(value) &&
|
|
299
|
+
typeof value.mutatesWorkspace === 'boolean' &&
|
|
300
|
+
typeof value.requiresApproval === 'boolean' &&
|
|
301
|
+
(value.destructive === undefined || typeof value.destructive === 'boolean') &&
|
|
302
|
+
(value.external === undefined || typeof value.external === 'boolean') &&
|
|
303
|
+
(value.privileged === undefined || typeof value.privileged === 'boolean') &&
|
|
304
|
+
(value.ambiguous === undefined || typeof value.ambiguous === 'boolean'));
|
|
305
|
+
}
|
|
306
|
+
function isCapability(value) {
|
|
307
|
+
return value === 'read' || value === 'edit' || value === 'shell' || value === 'git' || value === 'network' || value === 'memory' || value === 'sdd' || value === 'run' || value === 'mcp';
|
|
308
|
+
}
|
|
309
|
+
function isVerificationStatus(value) {
|
|
310
|
+
return value === 'not-run' || value === 'passed' || value === 'failed';
|
|
311
|
+
}
|
|
312
|
+
function isSessionStatus(value) {
|
|
313
|
+
return value === 'completed' || value === 'partial' || value === 'blocked' || value === 'failed' || value === 'cancelled';
|
|
314
|
+
}
|
|
315
|
+
function isFailedSessionStatus(value) {
|
|
316
|
+
return value === 'blocked' || value === 'failed' || value === 'cancelled';
|
|
317
|
+
}
|
|
318
|
+
function isOutcome(value) {
|
|
319
|
+
return value === 'success' || value === 'partial' || value === 'failure' || value === 'blocked' || value === 'cancelled';
|
|
320
|
+
}
|
|
@@ -38,30 +38,6 @@ export class SddWorkflowService {
|
|
|
38
38
|
const phases = this.getPhaseStatuses(validated.value.project, validated.value.change);
|
|
39
39
|
return statusFromPhases(validated.value.change, phases);
|
|
40
40
|
}
|
|
41
|
-
getDashboardStatus(input) {
|
|
42
|
-
const validated = validateProjectAndChange(input.project, input.change);
|
|
43
|
-
if (!validated.ok)
|
|
44
|
-
return validated;
|
|
45
|
-
const phases = this.getPhaseStatusesNoTrace(validated.value.project, validated.value.change);
|
|
46
|
-
if (!phases.ok)
|
|
47
|
-
return phases;
|
|
48
|
-
return statusFromPhases(validated.value.change, phases.value);
|
|
49
|
-
}
|
|
50
|
-
getPhaseStatusesNoTrace(project, change) {
|
|
51
|
-
const statuses = [];
|
|
52
|
-
for (const phase of sddPhases) {
|
|
53
|
-
const topicKey = sddTopicKey(change, phase);
|
|
54
|
-
const artifact = this.memory.getArtifactNoTrace(project, topicKey);
|
|
55
|
-
if (!artifact.ok) {
|
|
56
|
-
if (artifact.error.code !== 'not_found')
|
|
57
|
-
return artifact;
|
|
58
|
-
statuses.push({ phase, topicKey, present: false, state: 'missing', accepted: false, legacy: false });
|
|
59
|
-
continue;
|
|
60
|
-
}
|
|
61
|
-
statuses.push(phaseStatusFromArtifact(phase, topicKey, artifact.value));
|
|
62
|
-
}
|
|
63
|
-
return { ok: true, value: statuses };
|
|
64
|
-
}
|
|
65
41
|
getNext(input) {
|
|
66
42
|
const validated = validateProjectAndChange(input.project, input.change);
|
|
67
43
|
if (!validated.ok)
|
|
@@ -4,7 +4,7 @@ export const antigravitySetupAdapter = {
|
|
|
4
4
|
displayName: 'Antigravity',
|
|
5
5
|
supportLevel: 'placeholder',
|
|
6
6
|
capabilities: ['manual-guidance'],
|
|
7
|
-
targets: [{ kind: 'manual', label: 'Coming soon manual guidance',
|
|
7
|
+
targets: [{ kind: 'manual', label: 'Coming soon manual guidance', writableBySetup: false }],
|
|
8
8
|
getStatus() {
|
|
9
9
|
return {
|
|
10
10
|
providerId: 'antigravity',
|
|
@@ -5,7 +5,7 @@ export const claudeSetupAdapter = {
|
|
|
5
5
|
displayName: 'Claude',
|
|
6
6
|
supportLevel: 'preview-only',
|
|
7
7
|
capabilities: ['mcp-preview', 'manual-guidance'],
|
|
8
|
-
targets: [{ kind: 'manual', label: 'Claude MCP config snippet',
|
|
8
|
+
targets: [{ kind: 'manual', label: 'Claude MCP config snippet', writableBySetup: false }],
|
|
9
9
|
getStatus(context) {
|
|
10
10
|
return {
|
|
11
11
|
providerId: 'claude',
|
|
@@ -14,7 +14,7 @@ export const claudeSetupAdapter = {
|
|
|
14
14
|
evidence: context.databasePath !== undefined
|
|
15
15
|
? ['Claude MCP preview can be generated from the selected database path.']
|
|
16
16
|
: ['Claude MCP preview uses a placeholder until a database path is selected.'],
|
|
17
|
-
guidance: ['Copy snippets manually after reviewing them. The
|
|
17
|
+
guidance: ['Copy snippets manually after reviewing them. The TUI does not install or apply Claude config.'],
|
|
18
18
|
actions: [
|
|
19
19
|
{
|
|
20
20
|
id: 'claude-manual-guidance',
|
|
@@ -4,7 +4,7 @@ export const customSetupAdapter = {
|
|
|
4
4
|
displayName: 'Custom',
|
|
5
5
|
supportLevel: 'extension-point',
|
|
6
6
|
capabilities: ['manual-guidance'],
|
|
7
|
-
targets: [{ kind: 'extension', label: 'Custom provider extension point',
|
|
7
|
+
targets: [{ kind: 'extension', label: 'Custom provider extension point', writableBySetup: false }],
|
|
8
8
|
getStatus() {
|
|
9
9
|
return {
|
|
10
10
|
providerId: 'custom',
|
|
@@ -8,8 +8,8 @@ export const openCodeSetupAdapter = {
|
|
|
8
8
|
supportLevel: 'supported-primary',
|
|
9
9
|
capabilities: ['mcp-preview', 'mcp-install-plan', 'mcp-install-apply-external', 'agent-preview', 'doctor', 'visibility-check'],
|
|
10
10
|
targets: [
|
|
11
|
-
{ kind: 'user-config', label: 'User/global OpenCode config (default)', path: '$HOME/.config/opencode/opencode.json',
|
|
12
|
-
{ kind: 'project-config', label: 'Project OpenCode config (explicit opt-in)', path: '.opencode/opencode.json',
|
|
11
|
+
{ kind: 'user-config', label: 'User/global OpenCode config (default)', path: '$HOME/.config/opencode/opencode.json', writableBySetup: false },
|
|
12
|
+
{ kind: 'project-config', label: 'Project OpenCode config (explicit opt-in)', path: '.opencode/opencode.json', writableBySetup: false },
|
|
13
13
|
],
|
|
14
14
|
getStatus(context) {
|
|
15
15
|
const visibility = this.getVisibility?.(context);
|
|
@@ -161,7 +161,7 @@ function externalInstallAction(scope, targetPath) {
|
|
|
161
161
|
label: `Copy external OpenCode ${scope === 'user' ? 'user/global' : 'project'} install command`,
|
|
162
162
|
kind: 'copy-command',
|
|
163
163
|
command: ['vgxness', 'mcp', 'install', 'opencode', '--scope', scope, '--yes'],
|
|
164
|
-
description: 'Copy and run this outside the
|
|
164
|
+
description: 'Copy and run this outside the TUI only after reviewing the read-only plan.',
|
|
165
165
|
safety: externalProviderWriteSafety(targetPath),
|
|
166
166
|
};
|
|
167
167
|
}
|
|
@@ -183,7 +183,7 @@ function nextAction(store, mcp, defaults, providers, project) {
|
|
|
183
183
|
if (defaults.status !== 'ready') {
|
|
184
184
|
if (defaults.blocker?.startsWith('No project selected') === true) {
|
|
185
185
|
return {
|
|
186
|
-
command: 'vgx
|
|
186
|
+
command: 'vgx setup status --project <project>',
|
|
187
187
|
reason: defaults.nextAction ?? 'Select a project to verify project-scoped setup checks.',
|
|
188
188
|
};
|
|
189
189
|
}
|
package/docs/architecture.md
CHANGED
|
@@ -178,7 +178,7 @@ This plane should remain separate from runtime execution. Rendering OpenCode, Cl
|
|
|
178
178
|
|---|---|---|---|
|
|
179
179
|
| MCP server | AI coding tools and agents | Query/update workflow state, resolve agents/skills, record runs/checkpoints, request approvals. | Bypass permission policy or mutate provider config directly. |
|
|
180
180
|
| CLI | Human operators, scripts, CI-friendly automation | Initialize, inspect, doctor, sync/render assets, manage MCP installs, inspect SDD/runs/profiles. | Hide dangerous changes or require manual JSON editing for the happy path. |
|
|
181
|
-
| TUI | Humans during setup and local operation | Guide installation, show health/status,
|
|
181
|
+
| TUI | Humans during setup and local operation | Guide installation, show health/status, surface blockers. | Become decorative without next actions. |
|
|
182
182
|
|
|
183
183
|
### Natural-language planning seam
|
|
184
184
|
|
|
@@ -214,7 +214,7 @@ Current MCP tool groups are defined in source by `SUPPORTED_VGX_MCP_TOOL_NAMES`;
|
|
|
214
214
|
| Runs | `vgxness_run_start`, `vgxness_run_list`, `vgxness_run_get`, `vgxness_run_preflight`, `vgxness_run_checkpoint`, `vgxness_run_finalize` | Keep execution history, safety preflight, and resumability explicit. |
|
|
215
215
|
| Resolution/profile/payload | `vgxness_agent_resolve`, `vgxness_agent_activate`, `vgxness_manager_profile_get`, `vgxness_manager_profile_set`, `vgxness_skill_payload`, `vgxness_opencode_manager_payload` | Give agents the correct role, skills, model/profile context, and read-only OpenCode manager previews. Payload/profile preview tools do not execute providers or mutate provider config by default. |
|
|
216
216
|
|
|
217
|
-
MVP transport should prefer local stdio because it matches common MCP host expectations and avoids opening a network port. A local HTTP transport can be added later for
|
|
217
|
+
MVP transport should prefer local stdio because it matches common MCP host expectations and avoids opening a network port. A local HTTP transport can be added later for web-console or background-daemon use cases.
|
|
218
218
|
|
|
219
219
|
### CLI boundary
|
|
220
220
|
|
|
@@ -249,7 +249,7 @@ The TUI is the guided human surface. It should optimize for the next meaningful
|
|
|
249
249
|
| Doctor | Fix broken config, missing MCP integration, memory issues, and stale assets. |
|
|
250
250
|
| Settings | Review local preferences and configuration status without silent provider writes. |
|
|
251
251
|
|
|
252
|
-
Every TUI screen must define loading, empty, error, success, blocked, and permission states.
|
|
252
|
+
Every TUI screen must define loading, empty, error, success, blocked, and permission states. TUI status surfaces are read-only: provider config writes/install/apply are external-only, require explicit confirmation outside the TUI, and are not run by preview/status flows.
|
|
253
253
|
|
|
254
254
|
### Installation flow
|
|
255
255
|
|
|
@@ -693,6 +693,6 @@ Out of scope:
|
|
|
693
693
|
|
|
694
694
|
- cloud sync
|
|
695
695
|
- team collaboration
|
|
696
|
-
- web
|
|
696
|
+
- web console
|
|
697
697
|
- distributed workers
|
|
698
698
|
- fully autonomous skill mutation
|